Dynamic Razor code execution - how? - c#

In this post I wondered about cleaner code when internationalising an app. that leads to this second query... supposing I wanted to call a function like this:
#Html.RenderWithTags("Help",
new Dictionary<string, string>() { "HelpPage", "#Html.ActionLink(...)" }
)
such that I look up a string in my local resource file containing embedded "tags" e.g. resource name "Help" contains:
We suggest you read our [HelpPage]
before proceeding
and then my .RenderWithTags() method will expand the tags but dynamically executing the code in the dictionary passed e.g. replace [HelpPage] with whatever #Html.ActionLink(...) produces.
I know I can use Microsoft.CSharp.CSharpCodeProvider().CreateCompiler() to compile C# code on the fly, but what about Razor code?

This will be rather difficult to do.
Instead, you should put delegates in your dictionary.
For example:
new Dictionary<string, Func<string>>() {
{ "HelpPage", () => Html.ActionLink(...).ToString() }
}
If you're creating the dictionary in a Razor page, you could also use inline helpers:
new Dictionary<string, Func<Something, HelperResult>>() {
{ "HelpPage", #Html.ActionLink(...) }
}
This will allow to use arbitrary Razor markup in the values.
However, I would probably recommend that you create a single global dictionary in code, so that you don't need to repeat definitions across pages. (Depending on how you use it)

in the end, the solution turned out pretty slick. would not have been possible without SLaks, I'm much obliged for the help (though I didn't end up using inline helpers (but thanks for the intro (they're very cool))).
Now my page contains this:
#{
Dictionary<string, MvcHtmlString> tokenMap = new Dictionary<string, MvcHtmlString>() {
{"HelpPage", Html.ActionLink("help page", "Help", "Home") }
};
}
and somewhere below I have:
#this.Resource("Epilogue", tokenMap)
To accomplish this simplicity:
public static class PageExtensions
{
public static MvcHtmlString Resource(this WebViewPage page, string key)
{
HttpContextBase http = page.ViewContext.HttpContext;
string ret = (string) http.GetLocalResourceObject(page.VirtualPath, key);
return MvcHtmlString.Create(ret);
}
public static MvcHtmlString Resource(
this WebViewPage page, string key,
Dictionary<string, MvcHtmlString> tokenMap
) {
HttpContextBase http = page.ViewContext.HttpContext;
string text = (string) http.GetLocalResourceObject(page.VirtualPath, key);
return new TagReplacer(text, tokenMap).ToMvcHtmlString();
}
}
...and:
public class TagReplacer
{
Dictionary<string, MvcHtmlString> tokenmap;
public string Value { get; set; }
public TagReplacer(string text, Dictionary<string, MvcHtmlString> tokenMap)
{
tokenmap = tokenMap;
Regex re = new Regex(#"\[.*?\]", RegexOptions.IgnoreCase);
Value = re.Replace(text, new MatchEvaluator(this.Replacer));
}
public string Replacer(Match m)
{
return tokenmap[m.Value.RemoveSet("[]")].ToString();
}
public MvcHtmlString ToMvcHtmlString()
{
return MvcHtmlString.Create(Value);
}
}
...with a little extra help:
public static class ObjectExtensions
{
public static string ReplaceSet(this string text, string set, string x)
{
for (int i = 0; i < set.Length; i++)
{
text = text.Replace(set[i].ToString(), x);
}
return text;
}
public static string RemoveSet(this string text, string set)
{
return text.ReplaceSet(set, "");
}
}
comments or feedback on how it could have been better most welcome!

Related

ASP.NET MVC: Unit Test EditorFor generates an empty MvcHtmlString

I'm working on MVC 5 project and I have an unit test that checks the generated html markup for a custom html helper.
private class Faq
{
[RequiredIfMultiple(new string[] { "SickLeave", "Holidays" }, new object[] { 2, 2 })]
[RequiredIfMultiple(new string[] { "SickLeave1", "SickLeave2", "SickLeave3" }, new object[] { 2, 2, 2 })]
public string Property { get; set; }
}
[Test]
public void HtmlString_With_RequiredIfMultiple_Test()
{
//Arrange
Expression<Func<Faq, string>> expression = (t => t.Property);
//Act
IHtmlString result = htmlHelper.EditorForRequiredIf(expression);
// Assert
Assert.IsTrue(result.ToString().Contains("data-val-requiredifmultiple"));
}
The html helper extension EditorForRequiredIf
public static MvcHtmlString EditorForRequiredIf<TModel, TValue>(this HtmlHelper<TModel> helper
, Expression<Func<TModel, TValue>> expression
, string templateName = null
, string htmlFieldName = null
, object additionalViewData = null)
{
string mvcHtml = EditorExtensions.EditorFor(helper, expression, templateName, htmlFieldName, additionalViewData).ToString();
string element = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(ExpressionHelper.GetExpressionText(expression));
string key = helper.ViewData.Model.ToString() + "." + element;
if (RequiredIfMultipleAttribute.CountPerField != null)
{
RequiredIfMultipleAttribute.CountPerField.Remove(key);
if (RequiredIfMultipleAttribute.CountPerField.Count == 0)
{
RequiredIfMultipleAttribute.CountPerField = null;
}
}
string pattern = #"data\-val\-requiredif[a-z]+";
return Regex.IsMatch(mvcHtml, pattern) ? MergeClientValidationRules(mvcHtml) : MvcHtmlString.Create(mvcHtml);
}
Once in the view, the EditorFor calls GetClientValidationRules method on a custom attribute RequiredIfMultipleAttribute to generate the html markup with proper data-val tags.
GetClientValidationRules method:
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
int count = 0;
string key = metadata.ContainerType.FullName + "." + metadata.PropertyName;
if (CountPerField == null)
{
CountPerField = new Dictionary<string, int>();
}
if (CountPerField.ContainsKey(key))
{
count = ++CountPerField[key];
}
else
{
CountPerField.Add(key, count);
}
yield return new RequiredIfMultipleValidationRule(ErrorMessageString, Props, Vals, count);
}
So in production, all of this works perfectly. But during tests, I'm having an awful time.
What I get is an empty string on string mvcHtml = EditorExtensions.EditorFor(helper, expression, templateName, htmlFieldName, additionalViewData).ToString(); and on the calls stack, I don't see the method GetClientValidationRules has been called.
On the other hand, if I change EditorExtensions.EditorFor by InputExtensions.TextBoxFor, I see that the MvcHtmlString is correctly generated and GetClientValidationRules was called.
Does anyone have a clue? and I hope I was clear enough.
For the atttribute to work, some piece of code that executes your method has to recognize it and act on it in some way. When you are running your code in production that's what Asp.Net MVC does for you.
Your test is run by NUnit, not by Asp.Net. NUnit doesn't do anything with the attribute because it's not designed to do that. If you think about it, you'll realize that NUnit could not possibly recognize and act on all the various attributes known to all the hosts in which production code may run.
As a result, any use you make of that attribute has to be in your MVC application itself, not in your test.

Create a method which return a list of parameters (const string) from a class

I'm developing a class which contains some const strings
public static class Constants
{
public const string CarID= "car_id";
//public const string NumberID= "number_id"; // this is the second const string might be added, so
//the new created function can return the two
}
public class CarENParameters
{
public string Params { get; set; }
public CarENParameters(string carId)
{
Params = carId;
}
}
public static class CarPropertyProcess
{
//test params
public static CarENProps Parse(Uri uri,string content)
{
string carID= Regex.Matches(content, #"\$\('#CarXL'\)\.val\((\d+)\)", RegexOptions.None)[0].Groups[1].Value;
var parameters = new Dictionary<string, string>
{
{Constants.CarID, carID},
};
return new CarENProps(uri.AbsoluteUri, parameters);
}
public static CarENParameters GetParameters()
{
return new CarENParameters(Constants.CarID);
}
}
In the class Constants, I have one carID, now the case is it might have more than one const string like : public const string NumberID= "number_id";
So I want to create one function to return a list of those const strings, which are car_id and number_id with a class name CarENParameters but I havent figured out how to return a list by a get/set in a class, should I use dictionary or keyvaluespair to achieve that ? I'm quite new to C# so hope that I can have a better point of view from the helps of you guys. Thanks
Are you looking for something like this:
public static List<CarENParameters> GetParameters()
{
return new List<CarENParameters>()
{
new CarENParameters(Constants.CarID1),
new CarENParameters(Constants.CarID2),
new CarENParameters(Constants.CarID3)
}
}
You can use reflection for this
don't forget to put using System.Reflection;
// get class type
Type type = typeof(Constants);
// get a list of fields
FieldInfo[] fields = type.GetFields();
List<CarENParameters> list = new List<CarENParameters>();
// loop on field list
foreach (FieldInfo field in fields)
{
// if field is a string add it to our return list
if (field.FieldType == typeof(String))
list.Add(new CarENParameters((String) field.GetValue(null)));
}

C# Parse text file - access class field by index

I'd like to parse a text file with a few dozen entries. Right now, I have a dumbed-down solution that reads line by line and compares against hard-coded strings:
while ((line = reader.ReadLine()) != null) //returns null if end of stream
{
cmpStr = "MODE";
try
{
if (line.Equals(cmpStr))
GlobalData.mode = Convert.ToInt32(line.Remove(0, cmpStr.Length));
}
catch { }
cmpStr = "TIME_YEAR";
try
{
if (line.Equals(cmpStr))
GlobalData.time_year = Convert.ToInt32(line.Remove(0, cmpStr.Length));
}
catch { }
// ... repeat to parse the remaining lines
}
GlobalData is a static class and looks like this:
public static class GlobalData
{
public static int mode;
public static int time_year;
public static int time_month;
public static int time_day;
public static int time_hour;
public static int time_minute;
// other entries omitted
public static string[] GlobalKeywords = new string[37]
{
"MODE",
"TIME_YEAR",
"TIME_MONTH",
"TIME_DAY",
"TIME_HOUR",
"TIME_MINUTE",
// other entries omitted
};
}
If it were possible to access my static fields by index, I'd do:
int i = 0;
while ((line = reader.ReadLine()) != null)
{
cmpStr = GlobalData.GlobalKeywords[i]; // when i == 0: cmpStr = "MODE"
if (line.Equals(cmpStr))
GlobalData[i] = Convert.ToInt32(line.Remove(0, cmpStr.Length));
// GlobalData[0] would be GlobalData.mode, and so on (but doesn't work)
i++;
}
catch { }
So, even though I can setup a loop to compare against a string array of keywords,
how do I assign a certain field of my static class ?
br
Chris
I'm not sure what your business constraints are, so it's hard to propose a fool-proof solution, though a few points:
cmpStr = "MODE";
try
{
if (line.Equals(cmpStr))
GlobalData.mode = Convert.ToInt32(line.Remove(0, cmpStr.Length));
}
This won't work as you (probably expect) - if line.Equals("MODE") then line.Remove(0, "MODE".Length) is an empty string. What you probably want is line.StartsWith(cmpStr) or line.Contains(cmpStr).
GlobalData is a static class
This doesn't seem a good approach for what you're doing. You may want to read up on static classes and when to use them (MSDN is a good starting point, though it obviously can't cover everything: http://msdn.microsoft.com/en-us/library/79b3xss3%28v=vs.80%29.aspx).
Other than that, you can probably simply replace all your int fields with a dictionary (though please rethink the static approach as described above):
public static Dictionary<String, int> Items = new Dictionary<String, int>();
Then your parsing code could look like this:
while ((line = reader.ReadLine()) != null) //returns null if end of stream
{
var matchingString
= GlobalData.GlobalKeywords.FirstOrDefault(s => line.StartsWith(s));
if (matchingString != null)
GlobalData[matchingString]
= Convert.ToInt32(line.Remove(0, matchingString.Length));
}
You will then be able to fetch that data using e.g. GlobalData.Items["MODE"].
One last bit: you may consider introducing constant values in your global data class, e.g.:
public const String MODE = "MODE";
Then you can use GlobalData.Items[GlobalData.MODE] and avoid typos: writing GlobalData.Items[GlobalData.MODe] would cause a compile error.
Replace this:
public static int mode;
public static int time_year;
public static int time_month;
public static int time_day;
public static int time_hour;
public static int time_minute;
With this:
public static Dictionary<string, int> my_values = new Dictionary<string, int>();
Then replace:
GlobalData[i] = Convert.ToInt32(line.Remove(0, cmpStr.Length));
with:
GlobalData.my_values[cmpStr] = Convert.ToInt32(line.Remove(0, cmpStr.Length));
That should do what you want even though I don't understand how you expect the Convert.ToInt32 to work. The way you are calling Remove will create an empty string (which might convert to 0, I can't remember) and even if it didn't, the line doesn't contain a number because you compared it successfully to a string like "MODE".
An elegant way to solve your problem is to prepare a different action for each of the acceptable strings. You use a Dictionary(Of String, <Action>) where Action is a common delegate type that receive a string in input and know how to process it accordingly to the keyword present at the beginning of the line.
// The common signature for every methods stored in the value part of the dictionary
public delegate void ParseLine(string line);
// Global dictionary where you store the strings as keyword
// and the ParseLine as the delegate to execute
Dictionary<String, ParseLine> m_Actions = new Dictionary<String, ParseLine>() ;
void Main()
{
// Initialize the dictionary with the delegate corresponding to the strings keys
m_Actions.Add("MODE", new ParseLine(Task1));
m_Actions.Add("TIME_YEAR", new ParseLine(Task2));
m_Actions.Add("TIME_MONTH", new ParseLine(Task3));
m_Actions.Add("TIME_DAY", new ParseLine(Task4));
.....
while ((line = reader.ReadLine()) != null)
{
// Search the space that divide the keyword from the value on the same line
string command = line.Substring(0, line.IndexOf(' ')).Trim();
// a bit of error checking here is required
if(m_Actions.ContainsKey(command))
m_Actions[command](line);
}
}
void Task1(string line)
{
// this will handle the MODE line
GlobalData.Mode = Convert.ToInt32(line.Substring(line.IndexOf(' ')+1).Trim());
}
void Task2(string line)
{
GlobalData.time_year = Convert.ToInt32(line.Substring(line.IndexOf(' ')+1).Trim());
}
void Task3(string line)
{
.....
}
void Task4(string line)
{
.....
}
A simple (and not really clean) approach is to add an indexer to your global data class and decide which field to set based on the index. But you have to extend the indexer every time you add a field (basically you move the if/switch from the while Loop into the indexer).
You could also use reflection, if you can match the keyword to the field name. This is not very performant but does not need to be extended as long as you can map the keyword to the new field name.
Another approach is to create a dictionary>. In this dictionary you register the keywords, e.g. (pseudo-code):
Class Level variable:
private keywordsDict = new Dictionary<string, Action<int>>();
In a constructor:
keywordsDict.Add("MODE", delegate(value) GlobalData.mode = value);
In while-loop:
var action = keywordsDict[line];
action(value);
In the later approach, you only need to extend the dictionary but not the algorithm as such if you have a new keyword/field.
May be i can tell you how to achieve it (GlobalData[i]) in C# thought its not the answer you are looking for.
class GlobalData
{
private string[] array = new string[10];
public GlobalData()
{
//now initialize array
array[0] = "SomeThingA";
array[1] = "SomeThingB";//continue initialization.
}
public string this[int index]
{
get {return array[index];}
}
}
Now the clients can use GlobalData like ,
GlobalData gd = new GlobalData();
gd[1] = "SomeOtherThing" ; //set the value.
string value = gd[1];//get the value
But this cant be done by making the class static as you see it works with 'this'

What is the proper way to return two char arrays as a single string?

I have a public property in a static class which I want to use as a means of validating the user's input against illegal file and path names.
At present I have this:
private static string invalidFileNames = new string(Path.GetInvalidFileNameChars());
private static string invalidPathNames = new string(Path.GetInvalidPathChars());
private static string invalidUserInput = (invalidFileNames + invalidPathNames);
public static string InvalidUserInput
{
get { return invalidUserInput; }
}
Based on Microsoft's documentation here I am expecting to get back "<>|"<>| But all I am getting is the first "<>|.
Can anyone shed some light as to what is happening here? How do I ensure that I get both strings returned?
You could just make it a single string
using System.Linq;
public static string InvalidUserInput
{
get
{
return new string(Path.GetInvalidFileNameChars()
.Concat(Path.GetInvalidPathChars())
.Distinct()
.ToArray());
}
}
You wont see them all in a TextBox because of the terminator type chars in InvalidUserInput in your case its the \0(null teminator) that its stopping the display.
if you want to display just the ones that make sense to the user you can strip out the ones that cause the issue using Char.IsControl
Here is a static class to wrap it all up
public static class StringExtentions
{
private static string _invalidUserInput = string.Empty;
private static string _PrinatbleInvalidUserInput = string.Empty;
public static string InvalidUserInput
{
get
{
if (_invalidUserInput == string.Empty)
{
_invalidUserInput = new string(Path.GetInvalidFileNameChars()
.Concat(Path.GetInvalidPathChars())
.Distinct()
.ToArray());
}
return _invalidUserInput;
}
}
public static string GetPrinatbleInvalidUserInput
{
get
{
if (_PrinatbleInvalidUserInput == string.Empty)
{
_PrinatbleInvalidUserInput = new string(InvalidUserInput.Where(x => !char.IsControl(x)).ToArray());
}
return _PrinatbleInvalidUserInput;
}
}
public static bool IsValidUserInput(this string str)
{
return !str.Any(c => InvalidUserInput.Contains(c));
}
}
usage:
public MainWindow()
{
InitializeComponent();
string myString = "C:\\InvalidPath<";
if (!myString.IsValidUserInput())
{
MessageBox.Show(string.Format("String cannot contain {0}", StringExtentions.GetPrinatbleInvalidUserInput));
}
}
You can't see them in debugger, but you can output them to the file and see them with some better editor than notepad, like notepad++
File.WriteAllText("tmp.txt", invalidUserInput, UTF8Encoding.GetEncoding("UTF-8"));
In this case, because you are presumably going to be using the data numerous times, you would want to only record characters that are unique between both character arrays. To do this, you can use the Union method as follows:
private static string invalidUserInput = new string(Path.GetInvalidFileNameChars().Union(Path.GetInvalidPathChars()).ToArray());
Running your code gives a number of unicode characters returned:
"<>|□□□□□□□□□
□□
□□□□□□□□□□□□□□□□□□:*?\/"<>|□□□□□□□□□
□□
□□□□□□□□□□□□□□□□□□
Are you sure that you are not loosing anything due to the unicode characters?
Also, is that your exact code? If you switch the ordering of the variable initalizations it will no longer work as invalidUserInput must be evaluated after the other two (they are evaluated in the order they are defined in the code).

Trying to add JS and CSS to layout file in MVC 3 Razor website from partial views

Im currently using a method that looks like the following code to add script and css files to the head of the Layout file.
public static class HtmlHelperExtensions
{
public static MyCompanyHtmlHelpers MyCompany(this HtmlHelper htmlHelper)
{
return MyCompanyHtmlHelpers.GetInstance(htmlHelper);
}
}
public class MyCompanyHtmlHelpers
{
private static MyCompanyHtmlHelpers _instance;
public static MyCompanyHtmlHelpers GetInstance(HtmlHelper htmlHelper)
{
if (_instance == null)
_instance = new MyCompanyHtmlHelpers();
_instance.SetHtmlHelper(htmlHelper);
return _instance;
}
private HtmlHelper _htmlHelper;
public ItemRegistrar Styles { get; private set; }
public ItemRegistrar Scripts { get; private set; }
public MyCompanyHtmlHelpers()
{
Styles = new ItemRegistrar(ItemRegistrarFromatters.StyleFormat);
Scripts = new ItemRegistrar(ItemRegistrarFromatters.ScriptFormat);
}
private void SetHtmlHelper(HtmlHelper htmlHelper)
{
_htmlHelper = htmlHelper;
}
}
public class ItemRegistrar
{
private readonly string _format;
private readonly List<string> _items;
public ItemRegistrar(string format)
{
_format = format;
_items = new List<string>();
}
public ItemRegistrar Add(string url)
{
if (!_items.Contains(url))
_items.Insert(0, url);
return this;
}
public IHtmlString Render()
{
var sb = new StringBuilder();
foreach (var item in _items)
{
var fmt = string.Format(_format, item);
sb.AppendLine(fmt);
}
return new HtmlString(sb.ToString());
}
}
public class ItemRegistrarFromatters
{
public const string StyleFormat = "<link href=\"{0}\" rel=\"stylesheet\" type=\"text/css\" />";
public const string ScriptFormat = "<script src=\"{0}\" type=\"text/javascript\"></script>";
}
Using Html.MyCompany().Styles.Add("/Dashboard/Content/Dashboard.css"); to add files...
And #Html.MyCompany().Styles.Render() to render them in Layout_.cshtml.
My problem is that this is a static method meaning it persists the list of stylesheets and script files.
I need to do the same thing that this does but without keeping it persistant.
I need the lists to be remade on every request since they change from page to page what the look are on that specific page.
Is it possible to clear the lists on every request prior to adding the scripts that are needed or maybe after they have been rendered out?
Update:
The reason for not using a section, RenderPartial or RenderaActions is to prevent the same stylesheet or script file to be added more than once to the Layout file.
The site im building has a Layout_.cshtml with the basic layout. This in turn is used by a View that loops through a list of items and for each item a RenderAction is called that outputs the specific partial view for that item. These partial views sometimes need to add stylesheets and scripts.
As there can be needed to add many different scripts and stylesheets from different partial views a global list for styles and scripts were the only way i thought this could be done so there is a global place to check if a script is allready added to the collection or not and then render them all at once in the order they were added.
Update 2:
The real question is how to do the same kind of function (a global list) but without using a static Extension method.
I'd do this with sections, i.e.
#section head {
...add whatever you want here...
}
And render the "head" section from the layout:
<head>
...other stuff here...
#RenderSection("head", required: false)
</head>
If you don't want sections, and don't want to pass it around, I would use the HttpContext here; store some data against HttpContext.Current.Items[someKey]. If it is null, create a new one and store it in the context.
For example:
public static MyCompanyHtmlHelpers GetInstance(HtmlHelper htmlHelper)
{
const string key = "MyCompanyHtmlHelpersInstance";
IDictionary items = (htmlHelper == null || htmlHelper.ViewContext == null
|| htmlHelper.ViewContext.HttpContext == null)
? HttpContext.Current.Items : htmlHelper.ViewContext.HttpContext.Items;
MyCompanyHtmlHelpers obj = (MyCompanyHtmlHelpers)items[key];
if (obj == null)
{
items.Add(key, obj = new MyCompanyHtmlHelpers());
}
return obj;
}
What you need to do is extend the Html helper a little differently.
public static MvcHtmlString AddStyle(this HtmlHelper<TModel> html, string styleUrl)
{
string styleTag = string.Format("<link rel='stylesheet' type='text/css' href='{0}' />", styleUrl);
return MvcHtmlString.Create(styleTag);
}
Then in your view just do:
<head>
#Html.AddStyle("/Dashboard/Content/Dashboard.css")
</head>
If you need root relative URLs just throw in the Url content helper.
<head>
#Html.AddStyle(Url.Content("~/Dashboard/Content/Dashboard.css"))
</head>

Categories

Resources