If I have an Action like this:
public ActionResult DoStuff(List<string> stuff)
{
...
ViewData["stuff"] = stuff;
...
return View();
}
I can hit it with the following URL:
http://mymvcapp.com/controller/DoStuff?stuff=hello&stuff=world&stuff=foo&stuff=bar
But in my ViewPage, I have this code:
<%= Html.ActionLink("click here", "DoMoreStuff", "MoreStuffController", new { stuff = ViewData["stuff"] }, null) %>
Unfortunately, MVC is not smart enough to recognize that the action takes an array, and unrolls the list to form the proper url route. instead it just does a .ToString() on the object which just lists the data type in the case of a List.
Is there a way to get Html.ActionLink to generate a proper URL when one of the destination Action's parameters is an array or list?
-- edit --
As Josh pointed out below, ViewData["stuff"] is just an object. I tried to simplify the problem but instead caused an unrelated bug! I'm actually using a dedicated ViewPage<T> so I have a tightly coupled type aware Model. The ActionLink actually looks like:
<%= Html.ActionLink("click here", "DoMoreStuff", "MoreStuffController", new { stuff = ViewData.Model.Stuff }, null) %>
Where ViewData.Model.Stuff is typed as a List
I'm thinking that a custom HtmlHelper would be in order.
public static string ActionLinkWithList( this HtmlHelper helper, string text, string action, string controller, object routeData, object htmlAttributes )
{
var urlHelper = new UrlHelper( helper.ViewContext.RequestContext );
string href = urlHelper.Action( action, controller );
if (routeData != null)
{
RouteValueDictionary rv = new RouteValueDictionary( routeData );
List<string> urlParameters = new List<string>();
foreach (var key in rv.Keys)
{
object value = rv[key];
if (value is IEnumerable && !(value is string))
{
int i = 0;
foreach (object val in (IEnumerable)value)
{
urlParameters.Add( string.Format( "{0}[{2}]={1}", key, val, i ));
++i;
}
}
else if (value != null)
{
urlParameters.Add( string.Format( "{0}={1}", key, value ) );
}
}
string paramString = string.Join( "&", urlParameters.ToArray() ); // ToArray not needed in 4.0
if (!string.IsNullOrEmpty( paramString ))
{
href += "?" + paramString;
}
}
TagBuilder builder = new TagBuilder( "a" );
builder.Attributes.Add("href",href);
builder.MergeAttributes( new RouteValueDictionary( htmlAttributes ) );
builder.SetInnerText( text );
return builder.ToString( TagRenderMode.Normal );
}
you can suffix your routevalues with an array index like so:
RouteValueDictionary rv = new RouteValueDictionary();
rv.Add("test[0]", val1);
rv.Add("test[1]", val2);
this will result in the querystring containing test=val1&test=val2
that might help ?
Combining both methods works nicely.
public static RouteValueDictionary FixListRouteDataValues(RouteValueDictionary routes)
{
var newRv = new RouteValueDictionary();
foreach (var key in routes.Keys)
{
object value = routes[key];
if (value is IEnumerable && !(value is string))
{
int index = 0;
foreach (string val in (IEnumerable)value)
{
newRv.Add(string.Format("{0}[{1}]", key, index), val);
index++;
}
}
else
{
newRv.Add(key, value);
}
}
return newRv;
}
Then use this method in any extension method that requires routeValues with IEnumerable(s) in it.
Sadly, this workaround seams to be needed in MVC3 too.
This will just act as an extension to the UrlHelper and just provide a nice url ready to put anywhere rather than an an entire a tag, also it will preserve most of the other route values for any other specific urls being used... giving you the most friendly specific url you have (minus the IEnumerable values) and then just append the query string values at the end.
public static string ActionWithList(this UrlHelper helper, string action, object routeData)
{
RouteValueDictionary rv = new RouteValueDictionary(routeData);
var newRv = new RouteValueDictionary();
var arrayRv = new RouteValueDictionary();
foreach (var kvp in rv)
{
var nrv = newRv;
var val = kvp.Value;
if (val is IEnumerable && !(val is string))
{
nrv = arrayRv;
}
nrv.Add(kvp.Key, val);
}
string href = helper.Action(action, newRv);
foreach (var kvp in arrayRv)
{
IEnumerable lst = kvp.Value as IEnumerable;
var key = kvp.Key;
foreach (var val in lst)
{
href = href.AddQueryString(key, val);
}
}
return href;
}
public static string AddQueryString(this string url, string name, object value)
{
url = url ?? "";
char join = '?';
if (url.Contains('?'))
join = '&';
return string.Concat(url, join, name, "=", HttpUtility.UrlEncode(value.ToString()));
}
There is a librarly called Unbinder, which you can use to insert complex objects into routes/urls.
It works like this:
using Unbound;
Unbinder u = new Unbinder();
string url = Url.RouteUrl("routeName", new RouteValueDictionary(u.Unbind(YourComplexObject)));
I'm not at my workstation, but how about something like:
<%= Html.ActionLink("click here", "DoMoreStuff", "MoreStuffController", new { stuff = (List<T>)ViewData["stuff"] }, null) %>
or the typed:
<%= Html.ActionLink("click here", "DoMoreStuff", "MoreStuffController", new { stuff = (List<T>)ViewData.Model.Stuff }, null) %>
Related
My project has models with 2 or more words in the name:
EngineConfigurationModel
MyProductModel
CurrentProductModel
CheckNetworkInventoryModel
I've got an extension that can create a breadcrumb:
public static string BuildBreadcrumbNavigation(this HtmlHelper helper)
{
// optional condition: I didn't wanted it to show on home and account controller
if (helper.ViewContext.RouteData.Values["controller"].ToString() == "Home" ||
helper.ViewContext.RouteData.Values["controller"].ToString() == "Account")
{
return string.Empty;
}
var htmlLink = helper.ActionLink("Home", "Index", "Home").ToHtmlString();
var sb = new StringBuilder("<ol class='breadcrumb'><li>");
sb.Append(htmlLink);
sb.Append("</li>");
sb.Append("<li>");
sb.Append(helper.ActionLink(helper.ViewContext.RouteData.Values["controller"].ToString().Titleize(),
"", // "Index",
helper.ViewContext.RouteData.Values["controller"].ToString()));
sb.Append("</li>");
if (helper.ViewContext.RouteData.Values["action"].ToString() != "Index")
{
sb.Append("<li>");
sb.Append(helper.ActionLink(helper.ViewContext.RouteData.Values["action"].ToString().Titleize(),
helper.ViewContext.RouteData.Values["action"].ToString(),
helper.ViewContext.RouteData.Values["controller"].ToString()));
sb.Append("</li>");
}
var result = sb.Append("</ol>").ToString().Replace("Index", "");
return result;
}
Source: https://stackoverflow.com/a/26439510/153923
But, I want to split-up the words for project models with 2 or more words in the name.
for EngineConfigurationModel, class name EngineConfiguration would be 'Engine Configuration'
MyProductModel, class name MyProduct would be 'My Product'
CurrentProductModel, class name CurrentProduct would be 'Current Product'
CheckNetworkInventoryModel, class name CheckNetworkInventory would be 'Check Network Inventory'
For model properties with multiple words, I can use a [Display(Name = "some thing")] parameter like this:
[Display(Name = "Some Thing")]
public string SomeThing { get; set; }
I tried putting the Display attribute on the class declaration, but VS2022 says:
Attribute 'Display' is not valid on this declaration type. It is only valid on 'method, property, indexer, field, parameter' declarations.
I made something and I put it into an extension. It has gone through 2 revisions, but it seems to be flawless now.
Adding my work here for others:
public static string SplitTitleWords(this string value)
{
var cList = new List<char>();
if (!string.IsNullOrEmpty(value))
{
cList.Add(value[0]); // just add the first letter, whether caps, no caps, or number
for (var i = 1; i < value.Length; i++)
{
var c = value[i];
if (char.IsUpper(c))
{ // 01234567891234 0123456789012345
// check special cases like class AddPDFResource => Add PDF Resource
var c0 = value[i - 1];
if (char.IsUpper(c0))
{
if (i + 1 < value.Length)
{
var c1 = value[i + 1];
if (!char.IsUpper(c1))
{
cList.Add(' ');
}
}
} else
{
cList.Add(' ');
}
}
cList.Add(c);
}
}
var result = new String(cList.ToArray());
return result;
}
And here is a Breadcrumb extension method that calls it twice:
public static string BuildBreadcrumbNavigation(this HtmlHelper helper)
{
var result = string.Empty;
var controllerName = helper.ViewContext.RouteData.Values["controller"].ToString();
// optional condition: I didn't wanted it to show on home and account controller
if ((controllerName != "Home") && (controllerName != "Account"))
{
var homeLink = helper.ActionLink(
linkText: "Home",
actionName: "Index",
controllerName: "Home").ToHtmlString();
var sb = new StringBuilder($"<ol class='breadcrumb'><li>{homeLink}</li>");
var url = HttpContext.Current.Request.Url.ToString();
var urlParts = url.Split(new char[] { '/' });
if (!urlParts.Contains("Console"))
{
var controllerLink = helper.ActionLink(
linkText: controllerName.SplitTitleWords(),
actionName: "Index",
controllerName: controllerName);
sb.Append($"<li>{controllerLink}</li>");
} else
{
var a = $"Console";
sb.Append($"<li>{a}</li>");
}
var actionName = helper.ViewContext.RouteData.Values["action"].ToString();
sb.Append($"<li class=\"active\">{actionName.SplitTitleWords()}</li>");
result = sb.Append("</ol>").ToString();
}
return result;
}
It has been working great for me.
The Goal
I need a list view that can display any query I throw into it without having to create a custom view model or custom view.
The Solution
Using reflection, I can pass anonymous types to custom helper functions to create all headers and rows within a table.
The Problem
In order to get the anonymous type, I need to use the GetGenericArguments() method and use the [] operator to pull that info out. However, when I use a named Type (a model that I have hard-coded), the necessary index to grab that type from the IEnumerable<object> line is 0. When I use an anonymous type, the necessary index is 1. Why is this, and is there a better way I can get that Type?
The Code
Controller:
(I can pass any of these model queries to the view and the table displays accordingly)
public ActionResult Index()
{
var models = taskService.ReadMany().Select(m => new
{
Name = m.Title,
Summary = m.Details,
Importance = m.Priority,
DueDate = m.DateDue
});
var models = taskService.ReadMany();
var models = taskService.ReadMany().GroupBy(m => new { m.DateDue }).Select(m => new { NumberOfProjects = m.Count(), DueOn = m.Key.DateDue });
return View(models);
}
Helper Extensions:
public static MvcHtmlString BuildHeaders(this HtmlHelper html, Type type)
{
var tr = new TagBuilder("tr");
var model = type;
foreach (MemberInfo prop in model.GetProperties())
{
var th = new TagBuilder("th");
var display = prop.GetCustomAttribute(typeof(DisplayAttribute)) as DisplayAttribute;
if (display != null) th.InnerHtml = display.Name;
else if (!prop.Name.Contains("Id")) th.InnerHtml = prop.Name;
else continue;
tr.InnerHtml += th.ToString();
}
return MvcHtmlString.Create(tr.ToString());
}
public static MvcHtmlString BuildRow(this HtmlHelper html, object model)
{
var tr = new TagBuilder("tr");
var type = model.GetType();
foreach (PropertyInfo prop in type.GetProperties())
{
var td = new TagBuilder("td");
var val = prop.GetValue(model, null);
string valString = "";
if (val != null) valString = val.ToString();
if (!prop.Name.Contains("Id")) td.InnerHtml = valString;
else continue;
tr.InnerHtml += td.ToString();
}
return MvcHtmlString.Create(tr.ToString());
}
List View:
#model IEnumerable<object>
#{
ViewBag.Title = "Index";
Type type = Model.GetType().GetGenericArguments()[1]; //this line if I'm using an anonymous type
Type type = Model.GetType().GetGenericArguments()[0]; //this line if I'm using a named type. I need to make this line handle both situations without manual intervention
}
<h2>Index</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table class="table">
#Html.BuildHeaders(type)
#foreach (object item in Model) {
#Html.BuildRow(item)
}
</table>
This question already has answers here:
Is there a "String.Format" that can accept named input parameters instead of index placeholders? [duplicate]
(9 answers)
Closed 4 years ago.
I'm trying to format some string dynamically with available variables in a specific context/scope.
This strings would have parts with things like {{parameter1}}, {{parameter2}} and these variables would exist in the scope where I'll try to reformat the string. The variable names should match.
I looked for something like a dynamically string interpolation approach, or how to use FormattableStringFactory, but I found nothing that really gives me what I need.
var parameter1 = DateTime.Now.ToString();
var parameter2 = "Hello world!";
var retrievedString = "{{parameter2}} Today we're {{parameter1}}";
var result = MagicMethod(retrievedString, parameter1, parameter2);
// or, var result = MagicMethod(retrievedString, new { parameter1, parameter2 });
Is there an existing solution or should I (in MagicMethod) replace these parts in the retrievedString with matching members of the anonymous object given as parameter (using reflection or something like that)?
EDIT:
Finally, I created an extension method to handle this:
internal static string SpecialFormat(this string input, object parameters) {
var type = parameters.GetType();
System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex( "\\{(.*?)\\}" );
var sb = new System.Text.StringBuilder();
var pos = 0;
foreach (System.Text.RegularExpressions.Match toReplace in regex.Matches( input )) {
var capture = toReplace.Groups[ 0 ];
var paramName = toReplace.Groups[ toReplace.Groups.Count - 1 ].Value;
var property = type.GetProperty( paramName );
if (property == null) continue;
sb.Append( input.Substring( pos, capture.Index - pos) );
sb.Append( property.GetValue( parameters, null ) );
pos = capture.Index + capture.Length;
}
if (input.Length > pos + 1) sb.Append( input.Substring( pos ) );
return sb.ToString();
}
and I call it like this:
var parameter1 = DateTime.Now.ToString();
var parameter2 = "Hello world!";
var retrievedString = "{parameter2} Today we're {parameter1}";
var result = retrievedString.SpecialFormat( new { parameter1, parameter2 } );
Now, I don't use double braces anymore.
You can use reflection coupled with an anonymous type to do this:
public string StringFormat(string input, object parameters)
{
var properties = parameters.GetType().GetProperties();
var result = input;
foreach (var property in properties)
{
result = result.Replace(
$"{{{{{property.Name}}}}}", //This is assuming your param names are in format "{{abc}}"
property.GetValue(parameters).ToString());
}
return result;
}
And call it like this:
var result = StringFormat(retrievedString, new { parameter1, parameter2 });
While not understanding what is the dificulty you're having, I'm placing my bet on
Replace( string oldValue, string newValue )
You can replace your "tags" with data you want.
var parameter1 = DateTime.Now.ToString();
var parameter2 = "Hello world!";
var retrievedString = "{{parameter2}} Today we're {{parameter1}}";
var result = retrievedString.Replace("{{parameter2}}", parameter2).Replace({{parameter1}}, parameter1);
EDIT
Author mentioned that he's looking at something that will take parameters and iterate the list. It can be done by something like
public static void Main(string[] args)
{
//your "unmodified" srting
string text = "{{parameter2}} Today we're {{parameter1}}";
//key = tag(explicitly) value = new string
Dictionary<string, string> tagToStringDict = new Dictionary<string,string>();
//add tags and it's respective replacement
tagToStringDict.Add("{{parameter1}}", "Foo");
tagToStringDict.Add("{{parameter2}}", "Bar");
//this returns your "modified string"
changeTagWithText(text, tagToStringDict);
}
public static string changeTagWithText(string text, Dictionary<string, string> dict)
{
foreach (KeyValuePair<string, string> entry in dict)
{
//key is the tag ; value is the replacement
text = text.Replace(entry.Key, entry.Value);
}
return text;
}
The function changeTagWithText will return:
"Bar Today we're Foo"
Using this method you can add all the tags to the Dictionary and it'll replace all automatically.
If you know order of parameters, you can use string.Format() method (msdn). Then, your code will look like:
var parameter1 = DateTime.Now.ToString();
var parameter2 = "Hello world!";
var retrievedString = "{{0}} Today we're {{1}}";
var result = string.Format(retrievedString, parameter2, parameter1);
I have a rather complicated issue. I am trying to get a unique key from a method and its formal and actual parameters. The goal of the method, is to take a method call, and return a unique key based on 1) The name of the class and method and 2) The name and values of the parameters it is called with.
The method looks like this (sorry for all the details, but I can't find a sensible way to make the example smaller yet still explain my problem)
public class MethodKey
{
public static string GetKey<T>(Expression<Func<T>> method, params string[] paramMembers)
{
var keys = new Dictionary<string, string>();
string scope = null;
string prefix = null;
ParameterInfo[] formalParams = null;
object[] actual = null;
var methodCall = method.Body as MethodCallExpression;
if (methodCall != null)
{
scope = methodCall.Method.DeclaringType.FullName;
prefix = methodCall.Method.Name;
IEnumerable<Expression> actualParams = methodCall.Arguments;
actual = actualParams.Select(GetValueOfParameter<T>).ToArray();
formalParams = methodCall.Method.GetParameters();
}
else
{
// TODO: Check if the supplied expression is something that makes sense to evaluate as a method, e.g. MemberExpression (method.Body as MemberExpression)
var objectMember = Expression.Convert(method.Body, typeof (object));
var getterLambda = Expression.Lambda<Func<object>>(objectMember);
var getter = getterLambda.Compile();
var m = getter();
var m2 = ((System.Delegate) m);
var delegateDeclaringType = m2.Method.DeclaringType;
var actualMethodDeclaringType = delegateDeclaringType.DeclaringType;
scope = actualMethodDeclaringType.FullName;
var ar = m2.Target;
formalParams = m2.Method.GetParameters();
//var m = (System.MulticastDelegate)((Expression.Lambda<Func<object>>(Expression.Convert(method.Body, typeof(object)))).Compile()())
//throw new ArgumentException("Caller is not a method", "method");
}
// null list of paramMembers should disregard all parameters when creating key.
if (paramMembers != null)
{
for (var i = 0; i < formalParams.Length; i++)
{
var par = formalParams[i];
// empty list of paramMembers should be treated as using all parameters
if (paramMembers.Length == 0 || paramMembers.Contains(par.Name))
{
var value = actual[i];
keys.Add(par.Name, value.ToString());
}
}
if (paramMembers.Length != 0 && keys.Count != paramMembers.Length)
{
var notFound = paramMembers.Where(x => !keys.ContainsKey(x));
var notFoundString = string.Join(", ", notFound);
throw new ArgumentException("Unable to find the following parameters in supplied method: " + notFoundString, "paramMembers");
}
}
return scope + "¤" + prefix + "¤" + Flatten(keys);
}
private static object GetValueOfParameter<T>(Expression parameter)
{
LambdaExpression lambda = Expression.Lambda(parameter);
var compiledExpression = lambda.Compile();
var value = compiledExpression.DynamicInvoke();
return value;
}
}
Then, I have the following test, which works OK:
[Test]
public void GetKey_From_Expression_Returns_Expected_Scope()
{
const string expectedScope = "MethodNameTests.DummyObject";
var expected = expectedScope + "¤" + "SayHello" + "¤" + MethodKey.Flatten(new Dictionary<string, string>() { { "name", "Jens" } });
var dummy = new DummyObject();
var actual = MethodKey.GetKey(() => dummy.SayHello("Jens"), "name");
Assert.That(actual, Is.Not.Null);
Assert.That(actual, Is.EqualTo(expected));
}
However, if I put the () => dummy.SayHello("Jens") call in a variable, the call fails. Because I then no longer get a MethodCallExpression in my GetKey method, but a FieldExpression (subclass of MemberExpression. The test is:
[Test]
public void GetKey_Works_With_func_variable()
{
const string expectedScope = "MethodNameTests.DummyObject";
var expected = expectedScope + "¤" + "SayHello" + "¤" + MethodKey.Flatten(new Dictionary<string, string>() { { "name", "Jens" } });
var dummy = new DummyObject();
Func<string> indirection = (() => dummy.SayHello("Jens"));
// This fails. I would like to do the following, but the compiler
// doesn't agree :)
// var actual = MethodKey.GetKey(indirection, "name");
var actual = MethodKey.GetKey(() => indirection, "name");
Assert.That(actual, Is.Not.Null);
Assert.That(actual, Is.EqualTo(expected));
}
The Dummy class SayHello method definitions are trivial:
public class DummyObject
{
public string SayHello(string name)
{
return "Hello " + name;
}
public string Meet(string person1, string person2 )
{
return person1 + " met " + person2;
}
}
I have two questions:
Is there any way to send the variable indirection to MethodKey.GetKey, and get it as a MethodCallExpression type?
If not, how can I get the name and value of the method supplied if I get a MemberExpression instead? I have tried a few bits in the "else" part of the code, but haven't succeeded.
Any help is appreciated.
Thanks in advance, and sorry for the long post.
The problem is you are putting it into the wrong type of variable. Your method expects Expression<Func<T>> and you are using a variable of type Func<string> to store it. The following should fix your problem:
Expression<Func<string>> foo = () => dummy.SayHello("Jens");
var actual = MethodKey.GetKey<string>(foo, "name");
converting a .net Func<T> to a .net Expression<Func<T>> discusses the differences between a Func and an Expression<Func> and converting between the two and at a glance it says don't. The compiler makes them into totally different things. So make it the right thing at compile time and it should work fine.
If this isn't an option then possibly an overload that takes a Func instead of an Expression might work for you.
Note that in both cases I would pass the variable directly rather than trying to make it into a new expression in your call.
I have recently decided to write a generic Table html helper to generate tables for my models and other objects, I have used reflection to make it more generic by taking an IEnumerable argument as the table data and a Dictionary for the .
I want to use reflection or some other method to get the properties [DisplayName()] attribute from the models MetaData so that it does not need to be specified in a dictionary. However all methods I have tried seem to return null, so I have removed them from my code.
public static MvcHtmlString Table(this HtmlHelper htmlHelper, Dictionary<string, string> boundColumns, IEnumerable<object> objectData, string tagId, string className, string controllerName, string idProperty)
{
bool hasAction = !String.IsNullOrEmpty(idProperty);
bool hasData = objectData.Count() > 0;
UrlHelper urlHelper = new UrlHelper(htmlHelper.ViewContext.RequestContext);
Type objectDataType = hasData ? objectData.First().GetType() : null;
IEnumerable<PropertyInfo> objectDataProperties = hasData ? from propInfo in objectDataType.GetProperties()
where boundColumns.ContainsKey(propInfo.Name)
select propInfo : null;
// Thead
TagBuilder theadtr = new TagBuilder("tr");
foreach (string col in boundColumns.Values)
theadtr.InnerHtml = String.Format("{0}\n{1}", theadtr.InnerHtml, (new TagBuilder("th") { InnerHtml = col }).ToString());
if (hasAction)
theadtr.InnerHtml = String.Format("{0}\n{1}", theadtr.InnerHtml, new TagBuilder("th") { InnerHtml = "Action" });
TagBuilder thead = new TagBuilder("thead") { InnerHtml = theadtr.ToString() };
// Tfoot
TagBuilder tfoot = new TagBuilder("tfoot");
if (!hasData) // Warn that there was no data to be displayed.
{
TagBuilder tfoottd = new TagBuilder("td") { InnerHtml = "There is currently nothing to display." };
tfoottd.MergeAttribute("colspan", (hasAction ? (boundColumns.Count + 1) : boundColumns.Count).ToString());
tfoottd.MergeAttribute("style", "text-align:center");
tfoot.InnerHtml = (new TagBuilder("tr") { InnerHtml = tfoottd.ToString() }).ToString();
}
else // Display a pager & filter for navigating through large amounts of data.
{
// The button for navigating to the first page.
TagBuilder pagefirst = new TagBuilder("img");
pagefirst.MergeAttribute("id", String.Format("{0}-page-first", tagId));
pagefirst.MergeAttribute("class", "first");
pagefirst.MergeAttribute("alt", "First Page");
pagefirst.MergeAttribute("src", urlHelper.Content("~/Content/Style/Tables/Themes/Blue/resultset_first.png"));
pagefirst.MergeAttribute("style", "cursor:pointer; vertical-align:middle;");
// The button for navigating to the previous page.
TagBuilder pageprev = new TagBuilder("img");
pageprev.MergeAttribute("id", String.Format("{0}-page-prev", tagId));
pageprev.MergeAttribute("class", "prev");
pageprev.MergeAttribute("alt", "Previous Page");
pageprev.MergeAttribute("src", urlHelper.Content("~/Content/Style/Tables/Themes/Blue/resultset_previous.png"));
pageprev.MergeAttribute("style", "cursor:pointer; vertical-align:middle;");
// The button for navigating to the next page.
TagBuilder pagenext = new TagBuilder("img");
pagenext.MergeAttribute("id", String.Format("{0}-page-next", tagId));
pagenext.MergeAttribute("class", "next");
pagenext.MergeAttribute("alt", "Next Page");
pagenext.MergeAttribute("src", urlHelper.Content("~/Content/Style/Tables/Themes/Blue/resultset_next.png"));
pagenext.MergeAttribute("style", "cursor:pointer; vertical-align:middle;");
// The button for navigating to the last page.
TagBuilder pagelast = new TagBuilder("img");
pagelast.MergeAttribute("id", String.Format("{0}-page-last", tagId));
pagelast.MergeAttribute("class", "last");
pagelast.MergeAttribute("alt", "Last Page");
pagelast.MergeAttribute("src", urlHelper.Content("~/Content/Style/Tables/Themes/Blue/resultset_last.png"));
pagelast.MergeAttribute("style", "cursor:pointer; vertical-align:middle;");
// The display field for the pager status.
TagBuilder pagedisplay = new TagBuilder("input");
pagedisplay.MergeAttribute("id", String.Format("{0}-page-display", tagId));
pagedisplay.MergeAttribute("type", "text");
pagedisplay.MergeAttribute("class", "pagedisplay");
pagedisplay.MergeAttribute("disabled", "disabled");
pagedisplay.MergeAttribute("style", "width:12%;");
// The select for changing page size.
TagBuilder pagesize = new TagBuilder("select");
pagesize.MergeAttribute("id", String.Format("{0}-page-size", tagId));
pagesize.MergeAttribute("class", "pagesize");
pagesize.MergeAttribute("style", "width:12%;");
for (int i = 10; i <= 100; i += 10)
{
TagBuilder option = new TagBuilder("option") { InnerHtml = i.ToString() };
if (i == 10)
option.MergeAttribute("selected", "selected");
option.MergeAttribute("value", i.ToString());
pagesize.InnerHtml = String.Format("{0}\n{1}", pagesize.InnerHtml, option.ToString());
}
// The pager container.
TagBuilder pagediv = new TagBuilder("div") { InnerHtml = (new TagBuilder("form") { InnerHtml = String.Format("{0}\n{1}\n{2}\n{3}\n{4}\n{5}", pagefirst.ToString(), pageprev.ToString(), pagenext.ToString(), pagelast.ToString(), pagedisplay.ToString(), pagesize.ToString()) }).ToString() };
pagediv.MergeAttribute("id", String.Format("{0}-pager", tagId));
pagediv.MergeAttribute("style", "float:left; width:50%;");
// Filter Text Field
TagBuilder filterfield = new TagBuilder("input");
filterfield.MergeAttribute("id", String.Format("{0}-filter-field", tagId));
filterfield.MergeAttribute("type", "text");
filterfield.MergeAttribute("style", "width:30%;");
// The filter container.
TagBuilder filterdiv = new TagBuilder("div") { InnerHtml = (new TagBuilder("form") {InnerHtml = String.Format("Search: {0}", filterfield.ToString())}).ToString() };
filterdiv.MergeAttribute("id", String.Format("{0}-filter", tagId));
filterdiv.MergeAttribute("style", "float:right; width:50%;");
TagBuilder tfoottd = new TagBuilder("td") { InnerHtml = String.Format("{0}\n{1}", pagediv.ToString(), filterdiv.ToString()) };
tfoottd.MergeAttribute("colspan", (hasAction ? (boundColumns.Count + 1) : boundColumns.Count).ToString());
tfoottd.MergeAttribute("style", "text-align:center");
tfoot.InnerHtml = (new TagBuilder("tr") { InnerHtml = tfoottd.ToString() }).ToString();
}
// Tbody
TagBuilder tbody = new TagBuilder("tbody");
foreach (object o in objectData)
{
TagBuilder tbodytr = new TagBuilder("tr");
foreach (PropertyInfo p in objectDataProperties)
{
string val = "N/A";
object pval = p.GetValue(o, null);
if (pval != null)
val = pval.ToString();
tbodytr.InnerHtml = String.Format("{0}\n{1}", tbodytr.InnerHtml, (new TagBuilder("td") { InnerHtml = val }).ToString());
}
if (hasAction)
{
string id = objectDataType.GetProperty(idProperty).GetValue(o, null).ToString();
tbodytr.InnerHtml = String.Format(
"{0}\n{1}",
tbodytr.InnerHtml,
(new TagBuilder("td") { InnerHtml = Table_ActionLinks(htmlHelper, controllerName, id) }).
ToString());
}
tbody.InnerHtml = String.Format("{0}\n{1}", tbody.InnerHtml, tbodytr.ToString());
}
// Table
TagBuilder table = new TagBuilder("table") { InnerHtml = String.Format("{0}\n{1}\n{2}", thead.ToString(), tfoot.ToString(), tbody.ToString()) };
table.MergeAttribute("id", string.IsNullOrEmpty(tagId) ? String.Format("table-{0}", boundColumns.Count.ToString()) : tagId);
table.MergeAttribute("summary", "Generic data list");
if (!String.IsNullOrEmpty(className))
table.MergeAttribute("class", String.Format("{0} {1}", className, "tablesorter"));
else
table.MergeAttribute("class", "tablesorter");
// Enable Sorting/Searching
if (hasData)
{
TagBuilder sortscript = new TagBuilder("script") { InnerHtml = String.Format("$(document).ready(function(){{$(\"#{0}\").tablesorter().tablesorterPager({{container:$(\"#{1}\")}});}});", tagId, String.Format("{0}-pager", tagId)) };
TagBuilder searchscript = new TagBuilder("script") { InnerHtml = String.Format("$(document).ready(function(){{$(\"#{0}\").keyup(function(){{$.uiTableFilter($(\"#{1}\"), this.value);}})}});", String.Format("{0}-filter-field", tagId), tagId) };
sortscript.MergeAttribute("type", "text/javascript");
return new MvcHtmlString(String.Format("{0}\n{1}\n{2}", table.ToString(), sortscript.ToString(), searchscript.ToString()));
}
return new MvcHtmlString(table.ToString());
}
So basically I am looking to use as much reflection as possible to eliminate as many arguments to this method as possible.
Thanks,
Alex.
I'm not sure why your Display attribute retrieval isn't working. Here's what I use. It's from a method which retrieves the Display attribute from enum field values, but it's the same basic pattern that I use to retrieve any attribute from an object:
public static string GetDisplayName<T>( T toCheck )
{
Type enumType = typeof(T);
if( !enumType.IsEnum ) return null;
MemberInfo[] members = enumType.GetMember(toCheck.ToString());
if( ( members == null ) || ( members.Length != 1 ) ) return toCheck.ToString();
foreach( MemberInfo memInfo in members )
{
DisplayAttribute[] attrs = (DisplayAttribute[]) memInfo.GetCustomAttributes(typeof(DisplayAttribute), false);
if( ( attrs != null ) && ( attrs.Length == 1 ) ) return attrs[0].Name;
}
return toCheck.ToString();
}