1 using System.Collections.Generic;
2 using System.ComponentModel;
3 using System.Diagnostics;
4 using System.Diagnostics.CodeAnalysis;
5 using System.Drawing;
6 using System.Globalization;
7 using System.Security.Permissions;
8 using System.Web.Compilation;
9 using System.Web.Resources;
10 using System.Web.Routing;
11 using System.Web.UI;
12 using System.Web.UI.WebControls;
13 using System.Web.DynamicData.Util;
14 
15 namespace System.Web.DynamicData {
16     /// <summary>
17     /// <para>A control that displays links to table actions based on routing rules. It will not generate links for actions that are not
18     /// allowed by the routing rules. It can work in 3 modes: explicit, databinding to MetaTable, or databinding to a data row.</para>
19     /// <para>Databinding to MetaTable allows for creating links to actions for a collection of MetaTable objects (such as in the Default.aspx
20     /// page in the project templates)</para>
21     /// <para>Databinding to a data row allows for creating links to actions for data rows retrieved from a database. These are usually used with
22     /// Edit and Details actions.</para>
23     /// <para>Explicit mode allows for links to non-item-specific actions (like List and Insert) and is achieved by properly setting
24     /// ContextTypeName, Table, and Action properties. This is done in the PreRender phase if the NavigateUrl property is null (i.e. it has not
25     /// been set explicitly or did not get set in one of the databinding scenarios.)</para>
26     /// <para>Extra route parameters can be provided by declaring expando attributes on the controls markup.</para>
27     /// </summary>
28     [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "HyperLink", Justification="It's an extension of the HyperLink class")]
29     [DefaultProperty("Action")]
30     [ToolboxBitmap(typeof(DynamicHyperLink), "DynamicHyperLink.bmp")]
31     public class DynamicHyperLink : HyperLink, IAttributeAccessor {
32         private HttpContextBase _context;
33         private bool _dataBound;
34         private object _dataItem;
35         private Dictionary<string, string> _extraRouteParams = new Dictionary<string, string>();
36 
37         /// <summary>
38         /// The name of the action
39         /// </summary>
40         [TypeConverter(typeof(ActionConverter))]
41         [DefaultValue("")]
42         [Category("Navigation")]
43         [ResourceDescription("DynamicHyperLink_Action")]
44         public string Action {
45             get {
46                 object o = ViewState["Action"];
47                 return (o == null ? String.Empty: (string)o);
48             }
49             set {
50                 ViewState["Action"] = value;
51             }
52         }
53 
54         internal new HttpContextBase Context {
55             get {
56                 return _context ?? new HttpContextWrapper(base.Context);
57             }
58             set {
59                 _context = value;
60             }
61         }
62 
63         /// <summary>
64         /// The name of the context type
65         /// </summary>
66         [DefaultValue("")]
67         [Category("Navigation")]
68         [ResourceDescription("DynamicHyperLink_ContextTypeName")]
69         public string ContextTypeName {
70             get {
71                 object o = ViewState["ContextTypeName"];
72                 return ((o == null) ? String.Empty : (string)o);
73             }
74             set {
75                 ViewState["ContextTypeName"] = value;
76             }
77         }
78 
79         /// <summary>
80         /// The name of the column whose value will be used to populate the Text
81         /// property if it is not already set in data binding scenarios.
82         /// </summary>
83         [DefaultValue("")]
84         [Category("Navigation")]
85         [ResourceDescription("DynamicHyperLink_DataField")]
86         public string DataField {
87             get {
88                 object o = ViewState["DataField"];
89                 return ((o == null) ? String.Empty : (string)o);
90             }
91             set {
92                 ViewState["DataField"] = value;
93             }
94         }
95 
96         // for unit testing purposes
97         internal object Page_DataItem {
98             get {
99                 return _dataItem ?? Page.GetDataItem();
100             }
101             set {
102                 _dataItem = value;
103             }
104         }
105 
106         /// <summary>
107         /// The name of the table
108         /// </summary>
109         [DefaultValue("")]
110         [Category("Navigation")]
111         [ResourceDescription("DynamicHyperLink_TableName")]
112         public string TableName {
113             get {
114                 object o = ViewState["TableName"];
115                 return ((o == null) ? String.Empty : (string)o);
116             }
117             set {
118                 ViewState["TableName"] = value;
119             }
120         }
121 
122         [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#")]
OnDataBinding(EventArgs e)123         protected override void OnDataBinding(EventArgs e) {
124             base.OnDataBinding(e);
125 
126             if (DesignMode) {
127                 return;
128             }
129 
130             if (!String.IsNullOrEmpty(NavigateUrl)) {
131                 // stop processing if there already is a URL
132                 return;
133             }
134 
135             if (!String.IsNullOrEmpty(TableName) || !String.IsNullOrEmpty(ContextTypeName)) {
136                 throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
137                     DynamicDataResources.DynamicHyperLink_CannotSetTableAndContextWhenDatabinding, this.ID));
138             }
139 
140             object dataItem = Page_DataItem;
141             if (dataItem == null) {
142                 throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture,
143                     DynamicDataResources.DynamicHyperLink_CannotBindToNull, this.ID));
144             }
145 
146             MetaTable table = dataItem as MetaTable;
147             if (table != null) {
148                 BindToMetaTable(table);
149             } else {
150                 BindToDataItem(dataItem);
151             }
152 
153             _dataBound = true;
154         }
155 
BindToMetaTable(MetaTable table)156         private void BindToMetaTable(MetaTable table) {
157             string action = GetActionOrDefaultTo(PageAction.List);
158             NavigateUrl = table.GetActionPath(action, GetRouteValues());
159             if (String.IsNullOrEmpty(Text)) {
160                 Text = table.DisplayName;
161             }
162         }
163 
BindToDataItem(object dataItem)164         private void BindToDataItem(object dataItem) {
165             dataItem = Misc.GetRealDataItem(dataItem);
166             Debug.Assert(dataItem != null, "DataItem is null");
167             // Try to get the MetaTable from the type and if we can't find it then ---- up.
168             MetaTable table = Misc.GetTableFromTypeHierarchy(dataItem.GetType());
169             if (table == null) {
170                 throw new InvalidOperationException(String.Format(
171                     CultureInfo.CurrentCulture,
172                     DynamicDataResources.MetaModel_EntityTypeDoesNotBelongToModel,
173                     dataItem.GetType().FullName));
174             }
175 
176             string action = GetActionOrDefaultTo(PageAction.Details);
177             NavigateUrl = table.GetActionPath(action, GetRouteValues(table, dataItem));
178 
179             if (String.IsNullOrEmpty(Text)) {
180                 if (!String.IsNullOrEmpty(DataField)) {
181                     Text = DataBinder.GetPropertyValue(dataItem, DataField).ToString();
182                 } else {
183                     Text = table.GetDisplayString(dataItem);
184                 }
185             }
186         }
187 
188         [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#")]
OnPreRender(EventArgs e)189         protected override void OnPreRender(EventArgs e) {
190             base.OnPreRender(e);
191 
192             if (DesignMode) {
193                 if (!String.IsNullOrEmpty(NavigateUrl)) {
194                     NavigateUrl = "DesignTimeUrl";
195                 }
196                 return;
197             }
198 
199             // check both _dataBound and NavigateUrl cause NavigateUrl might be empty if routing/scaffolding
200             // does not allow a particular action
201             if (!_dataBound && String.IsNullOrEmpty(NavigateUrl)) {
202                 MetaTable table;
203                 try {
204                     table = GetTable();
205                 } catch (Exception exception) {
206                     throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, DynamicDataResources.DynamicHyperLink_CannotDetermineTable, this.ID), exception);
207                 }
208 
209                 if (table == null) {
210                     throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, DynamicDataResources.DynamicHyperLink_CannotDetermineTable, this.ID));
211                 }
212 
213                 var action = GetActionOrDefaultTo(PageAction.List);
214                 NavigateUrl = table.GetActionPath(action, GetRouteValues());
215             }
216         }
217 
GetRouteValues()218         private RouteValueDictionary GetRouteValues() {
219             var routeValues = new RouteValueDictionary();
220             foreach (var entry in _extraRouteParams) {
221                 string key = entry.Key;
222                 routeValues[key] = entry.Value;
223             }
224             return routeValues;
225         }
226 
GetRouteValues(MetaTable table, object row)227         private RouteValueDictionary GetRouteValues(MetaTable table, object row) {
228             RouteValueDictionary routeValues = GetRouteValues();
229             foreach (var pk in table.GetPrimaryKeyDictionary(row)) {
230                 routeValues[pk.Key] = pk.Value;
231             }
232             return routeValues;
233         }
234 
GetActionOrDefaultTo(string defaultAction)235         private string GetActionOrDefaultTo(string defaultAction) {
236             return String.IsNullOrEmpty(Action) ? defaultAction : Action;
237         }
238 
239         // internal for unit testing
GetTable()240         internal virtual MetaTable GetTable() {
241             MetaTable table;
242             if (!String.IsNullOrEmpty(TableName)) {
243                 table = GetTableFromTableName();
244             } else {
245                 table = DynamicDataRouteHandler.GetRequestMetaTable(Context);
246             }
247             return table;
248         }
249 
GetTableFromTableName()250         private MetaTable GetTableFromTableName() {
251             var tableName = TableName;
252             var contextTypeName = ContextTypeName;
253             Debug.Assert(!String.IsNullOrEmpty(tableName));
254 
255             if (!String.IsNullOrEmpty(contextTypeName)) {
256                 // context type allows to disambiguate table names
257                 Type contextType = BuildManager.GetType(contextTypeName, /* throwOnError */ true, /* ignoreCase */ true);
258                 MetaModel model = MetaModel.GetModel(contextType);
259                 MetaTable table = model.GetTable(tableName, contextType);
260                 return table;
261             } else {
262                 var table = DynamicDataRouteHandler.GetRequestMetaTable(Context);
263                 if (table == null) {
264                     return null;
265                 }
266                 return table.Model.GetTable(tableName);
267             }
268         }
269 
270         #region IAttributeAccessor Members
271 
IAttributeAccessor.GetAttribute(string key)272         string IAttributeAccessor.GetAttribute(string key) {
273             return (string)_extraRouteParams[key];
274         }
275 
IAttributeAccessor.SetAttribute(string key, string value)276         void IAttributeAccessor.SetAttribute(string key, string value) {
277             _extraRouteParams[key] = value;
278         }
279 
280         #endregion
281     }
282 }
283