public class UrlRoutingModule : IHttpModule
{
// Fields
private static readonly object _requestDataKey;
private RouteCollection _routeCollection;
// Methods
static UrlRoutingModule();
public UrlRoutingModule();
protected virtual void Dispose();
protected virtual void Init(HttpApplication application);
private void OnApplicationPostMapRequestHandler(object sender, EventArgs e);
private void OnApplicationPostResolveRequestCache(object sender, EventArgs e);
public virtual void PostMapRequestHandler(HttpContextBase context);
public virtual void PostResolveRequestCache(HttpContextBase context);
void IHttpModule.Dispose();
void IHttpModule.Init(HttpApplication application);
// Properties
public RouteCollection RouteCollection { get; set; }
private class RequestData
{
// Methods
public RequestData();
// Properties
public IHttpHandler HttpHandler { get; set; }
public string OriginalPath { get; set; }
}
}
protected virtual void Init(HttpApplication application)
{
application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache);
application.PostMapRequestHandler += new EventHandler(this.OnApplicationPostMapRequestHandler);
}
private void OnApplicationPostResolveRequestCache(object sender, EventArgs e)
{
HttpContextBase context = new HttpContextWrapper(((HttpApplication) sender).Context);
this.PostResolveRequestCache(context);
}
private void OnApplicationPostMapRequestHandler(object sender, EventArgs e)
{
HttpContextBase context = new HttpContextWrapper(((HttpApplication) sender).Context);
this.PostMapRequestHandler(context);
}
public virtual void PostResolveRequestCache(HttpContextBase context)
{
RouteData routeData = this.RouteCollection.GetRouteData(context);
if (routeData != null)
{
IRouteHandler routeHandler = routeData.RouteHandler;
if (routeHandler == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoRouteHandler, new object[0]));
}
if (!(routeHandler is StopRoutingHandler))
{
RequestContext requestContext = new RequestContext(context, routeData);
IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext);
if (httpHandler == null)
{
throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoHttpHandler, new object[] { routeHandler.GetType() }));
}
context.Items[_requestDataKey] = new RequestData { OriginalPath = context.Request.Path, HttpHandler = httpHandler };
context.RewritePath("~/UrlRouting.axd");
}
}
}
public virtual void PostMapRequestHandler(HttpContextBase context)
{
RequestData data = (RequestData) context.Items[_requestDataKey];
if (data != null)
{
context.RewritePath(data.OriginalPath);
context.Handler = data.HttpHandler;
}
}
- UrlRoutingModule. There is a special ASP.NET module registered in the web.config. This module gets invoked in the request processing pipeline. It will extract route data and reset http handler to MvcHandler. Note that it is MvcRouteHandler that is registered in the Route data in the Application_Start and return the actual handler MvcHandler.
public class UrlRoutingModule : IHttpModule { // Fields private static readonly object _requestDataKey; private RouteCollection _routeCollection; // Methods static UrlRoutingModule(); public UrlRoutingModule(); protected virtual void Dispose(); protected virtual void Init(HttpApplication application); private void OnApplicationPostMapRequestHandler(object sender, EventArgs e); private void OnApplicationPostResolveRequestCache(object sender, EventArgs e); public virtual void PostMapRequestHandler(HttpContextBase context); public virtual void PostResolveRequestCache(HttpContextBase context); void IHttpModule.Dispose(); void IHttpModule.Init(HttpApplication application); // Properties public RouteCollection RouteCollection { get; set; } private class RequestData { // Methods public RequestData(); // Properties public IHttpHandler HttpHandler { get; set; } public string OriginalPath { get; set; } } } protected virtual void Init(HttpApplication application) { application.PostResolveRequestCache += new EventHandler(this.OnApplicationPostResolveRequestCache); application.PostMapRequestHandler += new EventHandler(this.OnApplicationPostMapRequestHandler); } private void OnApplicationPostResolveRequestCache(object sender, EventArgs e) { HttpContextBase context = new HttpContextWrapper(((HttpApplication) sender).Context); this.PostResolveRequestCache(context); } private void OnApplicationPostMapRequestHandler(object sender, EventArgs e) { HttpContextBase context = new HttpContextWrapper(((HttpApplication) sender).Context); this.PostMapRequestHandler(context); } public virtual void PostResolveRequestCache(HttpContextBase context) { RouteData routeData = this.RouteCollection.GetRouteData(context); if (routeData != null) { IRouteHandler routeHandler = routeData.RouteHandler; if (routeHandler == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoRouteHandler, new object[0])); } if (!(routeHandler is StopRoutingHandler)) { RequestContext requestContext = new RequestContext(context, routeData); IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); if (httpHandler == null) { throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoHttpHandler, new object[] { routeHandler.GetType() })); } context.Items[_requestDataKey] = new RequestData { OriginalPath = context.Request.Path, HttpHandler = httpHandler }; context.RewritePath("~/UrlRouting.axd"); } } } public virtual void PostMapRequestHandler(HttpContextBase context) { RequestData data = (RequestData) context.Items[_requestDataKey]; if (data != null) { context.RewritePath(data.OriginalPath); context.Handler = data.HttpHandler; } }Note the following statement in the method PostResolveRequestCache
IRouteHandler routeHandler = routeData.RouteHandler
when you use MapRoute function you need to provide a IRouteHandler parameter. The default is MvcRouteHandler, which implements IRouteHandler and returns an instance MvcHandler.
public interface IRouteHandler { // Methods IHttpHandler GetHttpHandler(RequestContext requestContext); } protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) { return new MvcHandler(requestContext); }MvcHandler implements IHttpHandler, but it can not be mapped as a real handler since it does not have a parameterless constructor. There does exist a http handler called MvcHttpHandler which inherits from UrlRoutingHandler and allows you to map it to a real request with .mvc extension. But since UrlRoutingModule does everything for you MvcHttpHandler is typically not used with UrlRoutingModule. You can get more details in MSDN.
- MvcHandler. MvcHandler invokes the method ProcessRequest, which will initialize controller via controller factory and call IController.Execute(RequestContext).
protected internal virtual void ProcessRequest(HttpContextBase httpContext) { IController controller; IControllerFactory factory; this.ProcessRequestInit(httpContext, out controller, out factory); try { controller.Execute(this.RequestContext); } finally { factory.ReleaseController(controller); } } - Controller. Controller inherits from ControllBase, in which Execute is implemented but the majority of the workload is delegated to the protected virtual method ExecuteCore.
protected virtual void Execute(RequestContext requestContext) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } this.VerifyExecuteCalledOnce(); this.Initialize(requestContext); this.ExecuteCore(); } protected override void ExecuteCore() { this.PossiblyLoadTempData(); try { string requiredString = this.RouteData.GetRequiredString("action"); if (!this.ActionInvoker.InvokeAction(base.ControllerContext, requiredString)) { this.HandleUnknownAction(requiredString); } } finally { this.PossiblySaveTempData(); } } protected virtual void HandleUnknownAction(string actionName) { throw new HttpException(0x194, string.Format(CultureInfo.CurrentUICulture, MvcResources.Controller_UnknownAction, new object[] { actionName, base.GetType().FullName })); } - ActionInvoker. Default ActionInvoker is reflection based. The method InvokeAction looks like:
public virtual bool InvokeAction(ControllerContext controllerContext, string actionName) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } if (string.IsNullOrEmpty(actionName)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName"); } ControllerDescriptor controllerDescriptor = this.GetControllerDescriptor(controllerContext); ActionDescriptor actionDescriptor = this.FindAction(controllerContext, controllerDescriptor, actionName); if (actionDescriptor == null) { return false; } FilterInfo filters = this.GetFilters(controllerContext, actionDescriptor); try { AuthorizationContext context = this.InvokeAuthorizationFilters(controllerContext, filters.AuthorizationFilters, actionDescriptor); if (context.Result != null) { this.InvokeActionResult(controllerContext, context.Result); } else { if (controllerContext.Controller.ValidateRequest) { ValidateRequest(controllerContext); } IDictionary parameterValues = this.GetParameterValues(controllerContext, actionDescriptor); ActionExecutedContext context2 = this.InvokeActionMethodWithFilters(controllerContext, filters.ActionFilters, actionDescriptor, parameterValues); this.InvokeActionResultWithFilters(controllerContext, filters.ResultFilters, context2.Result); } } catch (ThreadAbortException) { throw; } catch (Exception exception) { ExceptionContext context3 = this.InvokeExceptionFilters(controllerContext, filters.ExceptionFilters, exception); if (!context3.ExceptionHandled) { throw; } this.InvokeActionResult(controllerContext, context3.Result); } return true; }There are some interesting points worth emphasis here:
- FindAction, where action selectors are applied
- Filters, how filters are ordered and executed
- Action paramters, how action parameters are prepared
- ActionResult, ActionResult could be resulted normally or when exception happens. If you want to communicate with users something meaningful you’d better supply with an ActionResult no matter it is an exception.
- How exceptions handled.
- Context objects. ASP.NET MVC Framework introduces a number of context objects including RequestContext, ControllerContext, ActionExecutingContext, ActionExecutedContext, etc.
- Attributes
- ModelBinder
- Filters. There are 4 types of filers: IActionFilter, IAuthorizationFilter, IExceptionFilter and IResultFilter.Note that Controller also implements these filters, but all of these methods do nothing. If Controller is a filter, it is executed first as seen on in the method GetFilters in the ActionInvoker.Authorization filters are executed in the first place. If the resulted ActionResult is not null the action will be skipped and the resulted authorization ActionResult will be invoked.
public interface IActionFilter { // Methods void OnActionExecuted(ActionExecutedContext filterContext); void OnActionExecuting(ActionExecutingContext filterContext); } public interface IAuthorizationFilter { // Methods void OnAuthorization(AuthorizationContext filterContext); } public interface IExceptionFilter { // Methods void OnException(ExceptionContext filterContext); } public interface IResultFilter { // Methods void OnResultExecuted(ResultExecutedContext filterContext); void OnResultExecuting(ResultExecutingContext filterContext); } protected virtual FilterInfo GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) { FilterInfo filters = actionDescriptor.GetFilters(); ControllerBase controller = controllerContext.Controller; AddControllerToFilterList(controller, filters.ActionFilters); AddControllerToFilterList(controller, filters.ResultFilters); AddControllerToFilterList(controller, filters.AuthorizationFilters); AddControllerToFilterList(controller, filters.ExceptionFilters); return filters; } private static void AddControllerToFilterList(ControllerBase controller, IList filterList) where TFilter: class { TFilter item = controller as TFilter; if (item != null) { filterList.Insert(0, item); } } - Action Parameters. In order to execute the specific action all the parameters of this action method must be prepared first. This is what function GetParameters does. You will see ModelBinders take place here. Note that parameter values are stored in a IDictionary object. Before invoking actions ActionFilters are executed first as seen in the mothed InvokeActionMethodWithFilters. All parameters obtained through ModelBinders are exposed to the public in ActionExecutingContext. This provide another opportunity to change values for action parameters via ActionFilter.
protected virtual IDictionary GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (ParameterDescriptor descriptor in actionDescriptor.GetParameters()) { dictionary[descriptor.ParameterName] = this.GetParameterValue(controllerContext, descriptor); } return dictionary; } protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) { Type parameterType = parameterDescriptor.ParameterType; IModelBinder modelBinder = this.GetModelBinder(parameterDescriptor); IValueProvider valueProvider = controllerContext.Controller.ValueProvider; string str = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName; Predicate propertyFilter = GetPropertyFilter(parameterDescriptor); ModelBindingContext bindingContext = new ModelBindingContext { FallbackToEmptyPrefix = parameterDescriptor.BindingInfo.Prefix == null, ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(null, parameterType), ModelName = str, ModelState = controllerContext.Controller.ViewData.ModelState, PropertyFilter = propertyFilter, ValueProvider = valueProvider }; return (modelBinder.BindModel(controllerContext, bindingContext) ?? parameterDescriptor.DefaultValue); } protected virtual ActionExecutedContext InvokeActionMethodWithFilters(ControllerContext controllerContext, IList filters, ActionDescriptor actionDescriptor, IDictionary parameters) { ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters); Func seed = => new ActionExecutedContext(controllerContext, actionDescriptor, false, null) { Result = this.InvokeActionMethod(controllerContext, actionDescriptor, parameters) }; return filters.Reverse().Aggregate>(seed, (next, filter) => => InvokeActionMethodFilter(filter, this.preContext, next))(); } public class ActionExecutingContext : ControllerContext { // Methods public ActionExecutingContext(); public ActionExecutingContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary actionParameters); // Properties public virtual ActionDescriptor ActionDescriptor { get; set; } public virtual IDictionary ActionParameters { get; set; } public ActionResult Result { get; set; } } internal static ActionExecutedContext InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func continuation) { filter.OnActionExecuting(preContext); if (preContext.Result != null) { return new ActionExecutedContext(preContext, preContext.ActionDescriptor, true, null) { Result = preContext.Result }; } bool flag = false; ActionExecutedContext filterContext = null; try { filterContext = continuation(); } catch (ThreadAbortException) { filterContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false, null); filter.OnActionExecuted(filterContext); throw; } catch (Exception exception) { flag = true; filterContext = new ActionExecutedContext(preContext, preContext.ActionDescriptor, false, exception); filter.OnActionExecuted(filterContext); if (!filterContext.ExceptionHandled) { throw; } } if (!flag) { filter.OnActionExecuted(filterContext); } return filterContext; } protected virtual ActionResult InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary parameters) { object actionReturnValue = actionDescriptor.Execute(controllerContext, parameters); return this.CreateActionResult(controllerContext, actionDescriptor, actionReturnValue); } protected virtual ActionResult CreateActionResult(ControllerContext controllerContext, ActionDescriptor actionDescriptor, object actionReturnValue) { if (actionReturnValue == null) { return new EmptyResult(); } return ((actionReturnValue as ActionResult) ?? new ContentResult { Content = Convert.ToString(actionReturnValue, CultureInfo.InvariantCulture) }); } public class ActionExecutedContext : ControllerContext { // Fields private ActionResult _result; // Methods public ActionExecutedContext(); public ActionExecutedContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor, bool canceled, Exception exception); // Properties public virtual ActionDescriptor ActionDescriptor { get; set; } public virtual bool Canceled { get; set; } public virtual Exception Exception { get; set; } public bool ExceptionHandled { get; set; } public ActionResult Result { get; set; } } - ActionResult. Normally ActionResult is considered to be the result like ViewResult when user submits a http request. However, there are some variations. You can change the ActionResult either in ActionExecutingContext or ActionExecutedContext. If the ActionResult in the ActionExecutingContext is set the actual action will bypass as seen in InvokeActionWithFilter. InvokeActionResultWithFilter is executed against the ActionResult of ActionExecutedContext. InvokeActionMethod will set the ActionResult in ActionExecutedContext, but you can change it in the ActionFilter. If your action method returns some type other than ActionResult it will convert to ContentResult, i.e., a string. Probably you can still change the ActionResult in the ActionResultFilter but this not encouraged. ActionResult.ExecuteResult mostly renders html text and returns nothing.
protected virtual ResultExecutedContext InvokeActionResultWithFilters(ControllerContext controllerContext, IList filters, ActionResult actionResult) { ResultExecutingContext preContext = new ResultExecutingContext(controllerContext, actionResult); Func seed = delegate { this.InvokeActionResult(controllerContext, actionResult); return new ResultExecutedContext(controllerContext, actionResult, false, null); }; return filters.Reverse().Aggregate>(seed, (next, filter) => => InvokeActionResultFilter(filter, this.preContext, next))(); } protected virtual void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult) { actionResult.ExecuteResult(controllerContext); } internal static ResultExecutedContext InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func continuation) { filter.OnResultExecuting(preContext); if (preContext.Cancel) { return new ResultExecutedContext(preContext, preContext.Result, true, null); } bool flag = false; ResultExecutedContext filterContext = null; try { filterContext = continuation(); } catch (ThreadAbortException) { filterContext = new ResultExecutedContext(preContext, preContext.Result, false, null); filter.OnResultExecuted(filterContext); throw; } catch (Exception exception) { flag = true; filterContext = new ResultExecutedContext(preContext, preContext.Result, false, exception); filter.OnResultExecuted(filterContext); if (!filterContext.ExceptionHandled) { throw; } } if (!flag) { filter.OnResultExecuted(filterContext); } return filterContext; } - View. The final http result comes from ActionResult.ExecuteResult under the ControllerContext. There are a number of special or predefined ActionResult such as EmptyResult, ContentResult, FileContentResult, FilePathResult, FileStreamResult, HttpUnauthorizedResult, JasonResult, JavaScriptResult, RedirectResult, RedirectToRouteResult, PartialViewResult, ViewResult, etc. When ViewResult is executed it is trying to find View via FindView method. ViewEngineResult combines both IViewEngine and IView. IViewEngine is responsible for finding and releasing views or partial views. IView will render the detailed html text.
public abstract class ActionResult { // Methods protected ActionResult(); public abstract void ExecuteResult(ControllerContext context); } public class EmptyResult : ActionResult { // Fields private static readonly EmptyResult _singleton = new EmptyResult(); // Methods public override void ExecuteResult(ControllerContext context) { } // Properties internal static EmptyResult Instance { get { return _singleton; } } } public class RedirectResult : ActionResult { // Methods public RedirectResult(string url) { if (string.IsNullOrEmpty(url)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "url"); } this.Url = url; } public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } if (context.IsChildAction) { throw new InvalidOperationException(MvcResources.RedirectAction_CannotRedirectInChildAction); } string url = UrlHelper.GenerateContentUrl(this.Url, context.HttpContext); context.Controller.TempData.Keep(); context.HttpContext.Response.Redirect(url, false); } // Properties public string Url { get; private set; } } public class RedirectToRouteResult : ActionResult { // Fields private RouteCollection _routes; // Methods public RedirectToRouteResult(RouteValueDictionary routeValues); public RedirectToRouteResult(string routeName, RouteValueDictionary routeValues); public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } if (context.IsChildAction) { throw new InvalidOperationException(MvcResources.RedirectAction_CannotRedirectInChildAction); } string str = UrlHelper.GenerateUrl(this.RouteName, null, null, this.RouteValues, this.Routes, context.RequestContext, false); if (string.IsNullOrEmpty(str)) { throw new InvalidOperationException(MvcResources.Common_NoRouteMatched); } context.Controller.TempData.Keep(); context.HttpContext.Response.Redirect(str, false); } // Properties public string RouteName { get; private set; } internal RouteCollection Routes { get; set; } public RouteValueDictionary RouteValues { get; private set; } } public class JavaScriptResult : ActionResult { // Methods public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } HttpResponseBase response = context.HttpContext.Response; response.ContentType = "application/x-javascript"; if (this.Script != null) { response.Write(this.Script); } } // Properties public string Script { get; set; } } public class HttpUnauthorizedResult : ActionResult { // Methods public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } context.HttpContext.Response.StatusCode = 0x191; } } public abstract class ViewResultBase : ActionResult { // Fields private TempDataDictionary _tempData; private ViewDataDictionary _viewData; private ViewEngineCollection _viewEngineCollection; private string _viewName; // Methods protected ViewResultBase(); public override void ExecuteResult(ControllerContext context); protected abstract ViewEngineResult FindView(ControllerContext context); // Properties public TempDataDictionary TempData { get; set; } public IView View { get; set; } public ViewDataDictionary ViewData { get; set; } public ViewEngineCollection ViewEngineCollection { get; set; } public string ViewName { get; set; } } public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } if (string.IsNullOrEmpty(this.ViewName)) { this.ViewName = context.RouteData.GetRequiredString("action"); } ViewEngineResult result = null; if (this.View == null) { result = this.FindView(context); this.View = result.View; } TextWriter output = context.HttpContext.Response.Output; ViewContext viewContext = new ViewContext(context, this.View, this.ViewData, this.TempData, output); this.View.Render(viewContext, output); if (result != null) { result.ViewEngine.ReleaseView(context, this.View); } } public class ViewEngineResult { // Methods public ViewEngineResult(IEnumerable searchedLocations); public ViewEngineResult(IView view, IViewEngine viewEngine); // Properties public IEnumerable SearchedLocations { get; private set; } public IView View { get; private set; } public IViewEngine ViewEngine { get; private set; } } public interface IView { // Methods void Render(ViewContext viewContext, TextWriter writer); } public interface IViewEngine { // Methods ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache); ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache); void ReleaseView(ControllerContext controllerContext, IView view); } - WebForm View. Web Form Views are a special view based on ASP.NET Page. WebFormViewEngine is responsible for managing it. You need to to
- Controller Factory, ActionInvoker and ViewEngine
How data is bound to action parameters? Model binder, that might be your answer, but this is not a complete answer. If you have data in Form, Route, and QueryString, no matter it is primitive type, or custom type, you can resort to default model binders or use your own model binder to bind these types of data to action parameters. On the other hand, data may come from other sources like TempData or even you can supply addition parameters based on the incoming parameters as exemplified in Phil Haack’s post Manipulating Action Method Parameters. Now the question becomes how ASP.NET MVC Framework find out action method parameters and how you can take good advantage of this process to hook up your logic and modularize your code?
One of syntax sugar in T-Sql, which not many people know about, is use inner join in update and delete statement. As in the post Join as Constraints, you can also use join to select records that you want to update or delete.
Here are some examples:
update t set t.col3 = r.col4 from table1 t inner join table2 r where t.col1 = r.col1
delete t from table1 t inner join table2 r where t.col1 = r.col1
Join is used to retrieve information from a set of correlated tables. This is especially true for tables having foreign keys and information split in more than one table. However, join can also be used to express constraints for a selective set of records.
- The most simple case is using in and not in. But in and not in are only used to constraint individual columns and their conditions can not hold concurrently.
- Exists and not exists. If you want multiple records to be constrained at the same time, you can use exist and not exist.
- In the case of exist it is simple equal to inner join. The joined table might be a table, subquery, or CTE expression
In practice I found that some people like to concatenate composite primary keys into a unique string and then apply method 1. This works but not clean. I did fix some errors resulted from typo. Moreover, it has additional overhead. As such, I don’t encourage this technique here.
Server Controls has a Visible property allowing you to control its presence in the web. I discussed its advantages in the post No Dynamic Server Controls. Interestingly each html element has a similar style attribute called display. If display is set to none any html element will disappear from the eyes of users. However, it is still accessible. Further if it is a server control like Button it can also trigger associated event handlers. I discovered this technique long time ago and put to use in various situations. Here are some examples.
- Produce Postback event. Some people use __DoPostback to achieve this purpose. This may cause issues when that function changed. You can create a non-display button and invoke it literally using document.getElementById(‘buttonId’).click().
- Refresh Web Form. If you want to refresh the web form and keep its position you can use a non-display button to quickly implement that.
- Trigger specific event at the server side. For example, you can create a html element like a link or a button, but when the link or button is clicked it will transfer click to a non-display button and then server side event handler will be executed. This is a quite useful technique since it allows you to use Button wherever it can not be simply placed. I used it to implement sorting in Repeater controls a lot.
If you know this trick just skip it, but if you don’t know you need to really appreciate it.
ASP.NET Web Forms introduce a UI component technology called Server Control. It is very object oriented, integrated with page life cycle and state management, design time look and feel and rendering html code in itself. This is one of the weapons in ASP.NET Web Forms that allows you to develop web application so fast and change and maintain your application at ease. So try to make good use of Server Controls at any time.
- Don’t dynamically add or remove server controls. Server Control is quite complex component and well integrated with page life cycle and ViewState management. If you don’t know them very well you may be in trouble. On the other hand, doing so brings no good to you. Simply by turning on/off the property Visible you can control whether it will be rendered and thus be present when user opens the page. This not only works for page level controls but columns within DataGrids and server controls nested in other controls without page-level instances.
- Transform a html tag into a Server Control if you want to dynamically control its presence. Simply add an attribute ranat=”server” and any plain html tag will become a Server Control. I use it for <tr> a lot to control whether that row will appear.
- Wrap a chunk of aspx code in a container. By doing so you can the visibility of the chunk of the code. The container could be PlaceHolder, Panel and any Server Controls produced in 2.
- Even invisible Server Control is useful. Invisible Server Controls still save and restore state information in ViewState. This is very useful. For example, I’d like to use an invisible Literal control to store primary key information.
- This technique is very important when it comes to template based controls such as DataGrid, Repeter, etc. You can transverse Control Tree to find the specific instance of the Server Control and manipulate all its properties including Visible.
Useful? Let me what you think.
PS. For some applications like content management system loading user controls dynamically is indispensable for various reasons like extensibility, user customization, etc. I will cover this in a separate post. Stay tuned!
ASP.NET MVC is catching more and more eyeballs now. I like it too, but I want to use it in the same speed as I do with ASP.NET Web Forms. As you may know Microsoft has done a lot job to make ASP.NET Web Form easier to use and thus enable developers to develop web applications like a breeze. Undoubtedly ASP.NET MVC comes with their edges and overcomes troubles that plagued developers for a long time, but there are still a couple of things that are easy in ASP.NET Web Forms but not easy in ASP.NET MVC. Here are a few:
- One Html form across the whole page
- AutoPostBack
- ViewState, i.e. carrying state information across page postback
- Built-in functions with server controls such as sorting and paging
ASP.NET MVC gives you full control of how to render html. Unlike ASP.NET Web Forms, it supports multiple html form in one page. Controller actions will eventually be mapped to either form actions or url links. The latter is easy, but mapping controller actions to form actions is not easy as it seems. Look at a simple form below and think about how to do the mapping.
Delete will delete individual record but Save and Approve will persist all changes. What do you think of mapping? The quickest way might be wrapping Delete button with a simple form which action corresponds to a method Delete in the controller and putting Save and Approve in an outer form. You will quickly find out that Delete will not work and Save and Approve can not use the same action in just one form.
Nested form will not work. Why Delete button will not get triggered? Simply because it is in a nested form. When you click Delete button it is the outermost form submitted. Now the problem becomes that all buttons compete for the same form action, which is definitely not what we had expected.
The Solution
Don’t worry, there are still solutions to this problem. To distinguish Save and Approve, controller action method can take two parameters btnSave and btnApprove. As such you can tell which button is clicked by checking which parameter is not null. I learned this trick in the book Pro ASP.NET MVC Framework. It is a very good book and worth recommendation.
For Delete, we will treat it differently than Save and Approve since it just works against individual record. Create a simple form outside the form above. Its action maps to a controller action method Delete. But Delete button we can use a simple JavaScript to transfer function to the Delete form.
<input type=”button” value=”Delete” onclick=”deleteRecord(345); return false;” />
function deleteRecord(id) { …, deleteForm.submit();}
This technique is very useful and we will talk more about it. At this point do you think ASP.NET MVC is still beautiful? May not. You may get some idea on how to create controller actions and test them at a different anger. At least you can not think of controller actions completely ignorant of views.
If you have better solutions feel free to let me know.
My interest in distributing computing can trace back to my school days. Distributing applications can be anywhere in the enterprise computing ranging from traditional client-server applications to the latest Web Service applications. I also heard a lot buzzwords around distributing applications like AOP, WCF, WF, NHibernate, Dependency Injection, etc. Yeah, technology is evolving so fast. All the technologies and tools can make building distributing applications a lot easier than it was before. However, it does not say that knowing the buzzwords is good enough to build a quality enterprise application. IMO, here is the list of things that you must keep in mind. Otherwise you will not do right with the new technologies.
- Component design
- Tracking
- Error handling
- Logging, where, when and what needs to be logged
- Debugging
- Performance
- Change management including version, deployment and hot fixes.
- Security
I will give details on each entry. Stay tuned!
Recently I got an assignment using Sql 2000 DTS to transform data from an Excel file to a database table. Frankly speaking it is not a difficult job. However, you will get outrageous when you are trying to map dozens of Excel columns to table columns one by one. Fortunately I found a trick that does the mapping automatically and thus tremendously simplify the job. Here is how it works:
- As you may know DTS can automatically take anything in the first row of the Excel file as column names. So insert a empty row if necessary and put table column names in the corresponding fields.
- Next, don’t transfer data directly to the target table. Create a temporary table having the same structure as the target table. Further you need to remove all the constraints such as PK, FK and allow columns null. As such DTS will not get blocked by unwanted values.
- Third, add as many cheat columns named Fn as you can. DTS will give anonymous Excel columns a name like F1, F2, …, F30 if you don’t give it a name in the first field of the column. As such, DTS will auto map columns for you.
- The remaining thing you will do is to remove the unwanted data and validate data such as no duplicate for PK, no null values if null is not allowed. After the validation you simply write a simple query to move the data from temporary table to the target table.
Useful? Give your comments. Thanks.
