public static class HtmlHelperExt { public static string GetDebugInfo(this HtmlHelper html) { return "Hello"; } public static void Debug(this HtmlHelper html) { var writer = html.ViewContext.HttpContext.Response.Output; writer.Write("hello
"); } }
Dec 17, 2009
A dummy example of html helper
Dec 9, 2009
definition of two kinds of test
- State-based testing (also called state verification)
- determines whether the exercised method worked correctly by examining the state of the system under test and its collaborators (dependencies) after the method is exercised.
- Interaction testing
- is testing how an object sends input to or receives input from other objects—how that object interacts with other objects.
Nov 12, 2009
ResourceManager notes
The benefit to ResourceManager is that it can smartly load differently resource based on the current UI culture. ResourceManager basically it is used to read embedded resource or file based resource. But it is extensible. It has three constructor, and one static factory method
public ResourceManager(String baseName, Assembly assembly) public ResourceManager(Type resourceSource); public ResourceManager(String baseName, Assembly assembly, Type usingResourceSet); public static ResourceManager CreateFileBasedResourceManager(String baseName, String resourceDir, Type usingResourceSet)
The most commonly used constructor is the first one. It try to read the embedded resource in the assembly, and the baseName is used to search inside of assembly. The second constructor basically does the same thing of first one. The type of resourceSource is the strongly type resource that is automatically generated by tool. This type has some static method to wrap the resource. Here is used to calcuated the base name, and assembly. Both of these two method use default RuntimeResourceSet to cache the resource. The third one allowed you specify the type of ResourceSet to cached the resource. This is useful if you have resource in different format. For example, if you baseName is "base", and dll name is "asm", the Resource manager will search in that dll for a resource named "asm.base.resources". By default it will use RuntimeResourceSet to cache the resource. But RuntimeResourceSet is internal, so that you can not use that. Following is clone of RuntimeResourceSet.
public RuntimeResourceSetClone(IResourceReader reader) : base(reader) { } public RuntimeResourceSetClone(string fileName) { ResourceReader reader = new ResourceReader(fileName); this.Reader = reader; } public RuntimeResourceSetClone(Stream stream) { ResourceReader reader = new ResourceReader(stream); this.Reader = reader; } //we can use this ResourceSet to cache the default resource format ResourceManager manager = new ResourceManager("UI", Assembly.GetExecutingAssembly(), typeof(RuntimeResourceSetClone)); Console.WriteLine(manager.GetString("Name"));
When you use resx file to manage resources, the compiler does not actually create resx format resource , and embed it in the assembly. What it does is to generate resource in defautlt resource format. Say you have UI.resx file, and the embedded resource name will be assemblyName.ui.resources. In case, you really want to use it in resources, you have to rename the extension to resources. The following shows how to read that. Please note the you need put the dll name with file name together.
//the loose file name is filename.resources //the dll name is dllname ResourceManager manager = new ResourceManager("dllname.filename", Assembly.GetExecutingAssembly(), typeof(ResXResourceSet)); Console.WriteLine(manager.GetString("Type"));
For embedded file resource, the neutral resource will be embedded in the main dll, for other resource, an assembly will be put in sub folder in the name of the culture (eg. zh-CN), the name of assembly is like mainAssemblyName.resources.dll.
The static factory method is used to read loose file in file system. One of the benefit using loose file is that saving memory, because resource is not loaded in the memory. All your resources file need to follow format, baseName.cultureName.resources . Yes, the extension has to be resources, even thought the content is not in resx format. Below is some example.
//if the resource format is default resource format, set usingResourceSet type to null, var man = ResourceManager.CreateFileBasedResourceManager("baseName", "folderName", null); Console.WriteLine(man.GetString("Name")); //if resource file is in resx format, (the extension need to be ressources) var man = ResourceManager.CreateFileBasedResourceManager("baseName", "folderName", typeof(ResXResourceSet));
The algorithm of ResourceManager depends on the UICulture heavily. First it locate the resource for current culture, if found return the value, if not fall back, to neutral culture, then invariant culture. For example, if current culture is zh-CN, it will try in the path of zh-CN, zh-CHS, Invariant. So to implement that, resource manager holds a ResourceSet for each availabe culture, depending how much culture the resources support. For each ResourceSet, it holds a Dictionary
To extend ResourceManager, we just need implement how the ResourceSet is created, and how the resource is read by IResourceReader. The default ResourceSet interface is designed for file based resource.
public ResourceSet(Stream stream) public ResourceSet(IResourceReader reader) public ResourceSet(String fileName)
Because of ResourceManager will use the same interface to create ResourceSet, if your solution is also file based, you just need a ResoursSet follow this constructor pattern. And you don't need to create a new ResourceManager, that is how Resx file is implemented. But if your solution is not file based, you need to SubClass your ResourceManager as well. ResourceSet will use IResourceReader to build its dictionary. IResourceReader is actually very simple Another drawback of ResourceManager is lack of creating or updating resource.
public interface IResourceReader : IEnumerable, IDisposable { void Close(); IDictionaryEnumerator GetEnumerator(); }
CurrentCulture can not be set to Neutural Culture
var c = CultureInfo.GetCultureInfo("en"); Console.WriteLine(c.Name); //en var c2 = CultureInfo.CreateSpecificCulture("en"); Console.WriteLine(c2); //en-US Thread.CurrentThread.CurrentCulture = c2; //ok //following with throw //System.NotSupportedException: Culture 'en' is a neutral culture. Thread.CurrentThread.CurrentCulture = c;
Because of this, the following code is necessary
if (Request.UserLanguages != null && Request.UserLanguages.GetLength(0) > 0) { CultureInfo cultureInfo = new CultureInfo(Request.UserLanguages[0]); Thread.CurrentThread.CurrentUICulture = cultureInfo; Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureInfo.Name); }
This code gets the user's language preference from the first element of the Request.UserLanguages array. If the user's settings are such that, Request.UserLanguages[0] will be "en-US". We create a new CultureInfo object and assign it to CurrentUICulture. The next line uses CultureInfo.CreateSpecificCulture to create a specific culture from a potentially neutral culture. In this example the culture will be no different (i.e., it will be "en-US"), but if the original language preference was "en", the specific culture would be "en-US". Recall that this step is necessary because CurrentCulture must be culture-specific. Later in this chapter, we implement a more sophisticated version of this first attempt that iterates through each of the language preferences looking for a language that matches the application's supported languages.
Nov 11, 2009
How Routing Works in asp.net mvc
The mvc stack start with the UrlRoutingModule. The module handle two HttpApplication event.
/* the order of application event is: OnBeginRequest OnAuthenticateRequest OnPostAuthenticateRequest OnAuthorizeRequest OnPostAuthorizeRequest OnResolveRequestCache OnPostResolveRequestCache --> handled by PostResolveRequestCache OnPostMapRequestHandler --> handled by PostMapRequestHandler OnAcquireRequestState OnPostAcquireRequestState OnPreRequestHandlerExecute Page_Load Event of the Page OnPostRequestHandlerExecute OnReleaseRequestState OnPostReleaseRequestState OnUpdateRequestCache OnPostUpdateRequestCache OnEndRequest OnPreSendRequestHeaders */ public virtual void PostResolveRequestCache(HttpContextBase context) { // Match the incoming URL against the route table RouteData routeData = RouteCollection.GetRouteData(context); // Do nothing if no route found if (routeData == null) { return; } // If a route was found, get an IHttpHandler from the route's RouteHandler IRouteHandler routeHandler = routeData.RouteHandler; if (routeHandler == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoRouteHandler)); } // This is a special IRouteHandler that tells the routing module to stop processing // routes and to let the fallback handler handle the request. if (routeHandler is StopRoutingHandler) { return; } RequestContext requestContext = new RequestContext(context, routeData); IHttpHandler httpHandler = routeHandler.GetHttpHandler(requestContext); if (httpHandler == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentUICulture, RoutingResources.UrlRoutingModule_NoHttpHandler, routeHandler.GetType())); } // Save data to be used later in pipeline context.Items[_requestDataKey] = new RequestData() { OriginalPath = context.Request.Path, HttpHandler = httpHandler }; // Rewrite path to something registered as a managed handler in IIS. This is necessary so IIS7 will // execute our managed handler (instead of say the static file handler). context.RewritePath("~/UrlRouting.axd"); } public virtual void PostMapRequestHandler(HttpContextBase context) { RequestData requestData = (RequestData)context.Items[_requestDataKey]; if (requestData != null) { // Rewrite the path back to its original value, so the request handler only sees the original path. context.RewritePath(requestData.OriginalPath); // Set Context.Handler to the IHttpHandler determined earlier in the pipeline. context.Handler = requestData.HttpHandler; } }
The route matching is handled by "RouteData routeData = RouteCollection.GetRouteData(context);". It search the Routes defined in start up event. If RouteData is not found, the handling will be passed back. Below is the code how RouteCollection handle the matching.
//RouteCollection member public RouteData GetRouteData(HttpContextBase httpContext) { //... code skip ... // Go through all the configured routes and find the first one that returns a match using (GetReadLock()) { foreach (RouteBase route in this) { RouteData routeData = route.GetRouteData(httpContext); if (routeData != null) { //the first matching wins return routeData; } } } return null; } //Route member public override RouteData GetRouteData(HttpContextBase httpContext) { // Parse incoming URL (we trim off the first two chars since they're always "~/") string requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo; RouteValueDictionary values = _parsedRoute.Match(requestPath, Defaults); if (values == null) { // If we got back a null value set, that means the URL did not match return null; } RouteData routeData = new RouteData(this, RouteHandler); // Validate the values if (!ProcessConstraints(httpContext, values, RouteDirection.IncomingRequest)) { return null; } // Copy the matched values foreach (var value in values) { routeData.Values.Add(value.Key, value.Value); } // Copy the DataTokens from the Route to the RouteData if (DataTokens != null) { foreach (var prop in DataTokens) { routeData.DataTokens[prop.Key] = prop.Value; } } return routeData; } public string Url { get { return _url ?? String.Empty; } set { // The parser will throw for invalid routes. We don't have to worry // about _parsedRoute getting out of sync with _url since the latter // won't get set unless we can parse the route. _parsedRoute = RouteParser.Parse(value); // If we passed the parsing stage, save the original URL value _url = value; } }
When the Url is set for a route, a _parsedRoute is created based on the Url, the _parseRoute is used to match the request url with the help of default values. Then the constraint is applied. If match is found, the RouteData can be returned. Before that DataTokens is copied to RouteData as well. DataToken is not actually used in the process of matching, we can think of DataToken is some predefined RouteData. RouteCollection has another use , to generate url based on routes. When search RouteData, constraints are also tested. Normally, it is an regular expression, but you can roll out your customized constraints, here is an article about this.
//route collection member public VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { requestContext = GetRequestContext(requestContext); // Go through all the configured routes and find the first one that returns a match using (GetReadLock()) { foreach (RouteBase route in this) { VirtualPathData vpd = route.GetVirtualPath(requestContext, values); if (vpd != null) { vpd.VirtualPath = GetUrlWithApplicationPath(requestContext, vpd.VirtualPath); return vpd; } } } return null; } public VirtualPathData GetVirtualPath(RequestContext requestContext, string name, RouteValueDictionary values) { requestContext = GetRequestContext(requestContext); if (!String.IsNullOrEmpty(name)) { RouteBase namedRoute; bool routeFound; using (GetReadLock()) { routeFound = _namedMap.TryGetValue(name, out namedRoute); } if (routeFound) { VirtualPathData vpd = namedRoute.GetVirtualPath(requestContext, values); if (vpd != null) { vpd.VirtualPath = GetUrlWithApplicationPath(requestContext, vpd.VirtualPath); return vpd; } return null; } else { throw new ArgumentException( String.Format( CultureInfo.CurrentUICulture, RoutingResources.RouteCollection_NameNotFound, name), "name"); } } else { return GetVirtualPath(requestContext, values); } }
You can choose a named route or use all routes to build a url. It delegate build url to Route.GetVirtualPath method
public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { // Try to generate a URL that represents the values passed in based on current // values from the RouteData and new values using the specified Route. BoundUrl result = _parsedRoute.Bind(requestContext.RouteData.Values, values, Defaults, Constraints); if (result == null) { return null; } // Verify that the route matches the validation rules if (!ProcessConstraints(requestContext.HttpContext, result.Values, RouteDirection.UrlGeneration)) { return null; } VirtualPathData vpd = new VirtualPathData(this, result.Url); // Add the DataTokens from the Route to the VirtualPathData if (DataTokens != null) { foreach (var prop in DataTokens) { vpd.DataTokens[prop.Key] = prop.Value; } } return vpd; }
Route is importaint object, it is used in url generation. It has Constraints, DataTokens, Defaults properties.
Nov 10, 2009
HttpContextBase.Items
When you using controller, you can easily access HttpContext property, which is not an System.Web.HttpContext, but a HttpContextBase object. This object has a handy Items dictionary, which can allowed you share data over a the whole request.
TempData of Controller
TempData is property of Controller. The TempData is used to save the state from page redirected from to the page redirected. So let say, your request is handled by controller1.method1, for some reason, it should be redirected to controller2.method2. Before the redirection take place, you can save some data in the TempData, then http status code 302 and new url are returned to browser, and browser make a new request using new url. In the new action method, the TempData can be restored. This behavior is showed in the following code in the Controller
//Controller member protected override void ExecuteCore() { TempData.Load(ControllerContext, TempDataProvider); try { string actionName = RouteData.GetRequiredString("action"); if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) { HandleUnknownAction(actionName); } } finally { TempData.Save(ControllerContext, TempDataProvider); } }
This piece of code does not show how data is actually load and save, the job is actually performed by TempDataProvider. And the default provider is SessionStateTempDataProvider. We can override this TempDataProvider by injecting this property using custom ControllerFactory.
//Controller member public ITempDataProvider TempDataProvider { get { if (_tempDataProvider == null) { _tempDataProvider = new SessionStateTempDataProvider(); } return _tempDataProvider; } set { _tempDataProvider = value; } } //TempDataDictionary members public void Load(ControllerContext controllerContext, ITempDataProvider tempDataProvider) { IDictionary<string, object> providerDictionary = tempDataProvider.LoadTempData(controllerContext); _data = (providerDictionary != null) ? new Dictionary<string, object>(providerDictionary, StringComparer.OrdinalIgnoreCase) : new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); _initialKeys = new HashSet<string>(_data.Keys); _modifiedKeys.Clear(); } public void Save(ControllerContext controllerContext, ITempDataProvider tempDataProvider) { //this is very important, if there is no modified, we don't need to save it //for next use if (_modifiedKeys.Count > 0) { // Apply change tracking. foreach (string x in _initialKeys) { if (!_modifiedKeys.Contains(x)) { _data.Remove(x); } } // Store the dictionary tempDataProvider.SaveTempData(controllerContext, _data); } } public class SessionStateTempDataProvider : ITempDataProvider { internal const string TempDataSessionStateKey = "__ControllerTempData"; public virtual IDictionary<string, object> LoadTempData(ControllerContext controllerContext) { HttpContextBase httpContext = controllerContext.HttpContext; if (httpContext.Session == null) { throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled); } Dictionary<string, object> tempDataDictionary = httpContext.Session[TempDataSessionStateKey] as Dictionary<string, object>; if (tempDataDictionary != null) { // If we got it from Session, remove it so that no other request gets it httpContext.Session.Remove(TempDataSessionStateKey); return tempDataDictionary; } else { return new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); } } public virtual void SaveTempData(ControllerContext controllerContext, IDictionary<string, object> values) { HttpContextBase httpContext = controllerContext.HttpContext; if (httpContext.Session == null) { throw new InvalidOperationException(MvcResources.SessionStateTempDataProvider_SessionStateDisabled); } httpContext.Session[TempDataSessionStateKey] = values; } }
Extension point in asp.net mvc
-
Extending route.
Route matching is first step mvc framework, we can customize how route is matched by adding constraint, and default, and dataToken. We can implement IRouteConstraint. Here is ASP.NET MVC Tip #30 – Create Custom Route Constraints
-
Extending IRouteHandler
When we register route, by default the route handler is MvcRouteHandler. We can create a class implmenting IRouteHandler, of we can sub class MvcRouteHandler
-
Exending MvcHandler
MvcHandler is IHttpHandler, we can substitute with a MvcHandler sub class, or a complete new IHttpHandler , or override MvcHandler's ProcessRequest method. We can override ProcessRequest(HttpContextBase httpContext) method.
-
Extending ControllerFactory and Controller. We can decide how a controller is created. A dummy example is as follow. But you can create a controller by using IoC container.
ControllerBuilder.Current.SetControllerFactory(new ObjectControllerFactory()); public class ObjectControllerFactory:DefaultControllerFactory { public override IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName) { if (controllerName.ToLower() == "object") { return new ObjectController(new object()); } else { return base.CreateController(requestContext, controllerName); } } }
-
Extending the ActionResult.
-
Extending the Controller.
Extending ModelBinder
Extending ViewResult
Extending ViewEngine, and IView
We can subclass the default WebFormViewEngine, or implement and IViewEngine. The interesting method is FindView and FindPartialView.
-
Extending ViewPage or ViewUserControl
Extending HtmlHelper by adding HtmlHelper extension method.
How a view is found
There are some confusing names here. ViewResult is kind of ActionResult. It is return by an object of Controller class. ViewResult uses VewEngineCollection, a collection of IViewEngine, to find a ViewEngineResult. ViewEngineResult may or may not contain an IView. IView is implemented by ViewPage, ViewUserControl.
After a action result is returned from the action method, if it is ViewResult. The render method will find an ViewEngineResult through ViewEngineCollection object. The following code show how ViewEngineCollection finds ViewEngineResult, ViewEngineCollection enumerate all the ViewEngine to fine ViewEngineResult.
//ViewEngineCollection member private ViewEngineResult Find(Func<IViewEngine, ViewEngineResult> cacheLocator, Func<IViewEngine, ViewEngineResult> locator) { ViewEngineResult result; foreach (IViewEngine engine in Items) { if (engine != null) { result = cacheLocator(engine); if (result.View != null) { return result; //With IView } } } List<string> searched = new List<string>(); foreach (IViewEngine engine in Items) { if (engine != null) { result = locator(engine); if (result.View != null) { return result; //With IView } searched.AddRange(result.SearchedLocations); } } //a ViewEngineResult with no IView return new ViewEngineResult(searched); }
If the ViewEngineResult return by by IViewEngine has an IView object, it will be returned immediately, it will be returned immediately to the ViewResult caller. If you want to use your special IView object, you need to add IViewEngine to locate your IView. If there is no ViewEngineResult with an IView is found, return new ViewEngineResult with no IView to caller ViewResult, let see how ViewResult handle this.
protected override ViewEngineResult FindView(ControllerContext context) { ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName); if (result.View != null) { return result; } // we need to generate an exception containing all the locations we searched StringBuilder locationsText = new StringBuilder(); foreach (string location in result.SearchedLocations) { locationsText.AppendLine(); locationsText.Append(location); } throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, MvcResources.Common_ViewNotFound, ViewName, locationsText)); }
It throw an exception. If a ViewEngineResult with IView is returned, ViewResult will render the IView.
//ViewResultBase member public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } if (String.IsNullOrEmpty(ViewName)) { ViewName = context.RouteData.GetRequiredString("action"); } ViewEngineResult result = null; if (View == null) { result = FindView(context); View = result.View; } ViewContext viewContext = new ViewContext(context, View, ViewData, TempData); View.Render(viewContext, context.HttpContext.Response.Output); if (result != null) { result.ViewEngine.ReleaseView(context, View); } }
Here is example to how to implement an IView.
public class StaticView : IView { public void Render(ViewContext viewContext, System.IO.TextWriter writer) { writer.Write("Hello world
"); } } public class StaticViewEngine : IViewEngine { public ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache) { ViewEngineResult result = new ViewEngineResult(new StaticView(), this); return result; } public ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) { ViewEngineResult result = new ViewEngineResult(new StaticView(), this); return result; } public void ReleaseView(ControllerContext controllerContext, IView view) { //do nothing } } protected void Application_Start() { RegisterRoutes(RouteTable.Routes); ViewEngines.Engines.Insert(0, new StaticViewEngine()); }
Nov 9, 2009
Context data in asp.net mvc
Along the asp.net mvc stack, there are lots context data being passing around. The first context data is passed into MvcRouteHandler by the System.Web.Routing.UrlRoutingModule.
public class RequestContext { public RequestContext(HttpContextBase httpContext, RouteData routeData); public HttpContextBase HttpContext { get; } public RouteData RouteData { get; } }
Then the RequestContext is passed into MvcHandler via constructor. When MvcHandler.ProcessRequest is called, it wrap the httpContext into HttpContextBase, which is System.Web.Abstractions class. The reason to use HttpContext to replace the dependency on HttpContext object, which is sealed, with dependency on HttpContextBase which is an abstraction.
//MvcHandler member public MvcHandler(RequestContext requestContext) { if (requestContext == null) { throw new ArgumentNullException("requestContext"); } RequestContext = requestContext; } protected virtual void ProcessRequest(HttpContext httpContext) { HttpContextBase iHttpContext = new HttpContextWrapper(httpContext); ProcessRequest(iHttpContext); } protected internal virtual void ProcessRequest(HttpContextBase httpContext) { //skip IController controller = factory.CreateController(RequestContext, controllerName); //skip controller.Execute(RequestContext); }
When creating Controller, the RequestContext is passed into the ControllerFactory.CreateController method, after the controller is created the RequestContext is passed into controller.Execute method. By default, the controller is a System.Web.Mvc.Controller. A ControllerContext is created based on the RequestContext, the controller itself. A UrlHelper is also created with RequestContext. So nowt the Controller has a ControllerContext, and UrlHelper.
//Controller member protected override void Initialize(RequestContext requestContext) { base.Initialize(requestContext); Url = new UrlHelper(requestContext); } //ControllerBase member protected virtual void Initialize(RequestContext requestContext) { ControllerContext = new ControllerContext(requestContext, this); }
Then ControllerContext is used to create ControllerDescriptor, and ActionDescriptor, and FilterInfo(Filters), AuthorizationContext, parameters for actio method. ActionExecutedContext. It is also used in InvokeActionResultWithFilters method. Eventually, ActionResult use ControllerContext to ExecuteResult. ContextController is used to find ViewEngineResult
public abstract class ActionResult { public abstract void ExecuteResult(ControllerContext context); }
A ViewContext is created by using ControllerContext, and IView use viewContext to render view. ViewContext.ViewData is passed into ViewPage or ViewUserControl
acton parameters
The asp.net mvc framework matches action parameters in the following order:
- Form values
- Route arguments
- Querystring parameters
The parameters that is passed in the action method is handled by ControllerActionInvoker.The ControllerActionInvoker tries to using reflection and other techniques to extract information for client's request, as the source code shows below.
//ControllerActionInvoker members public bool InvokeAction(ControllerContext controllerContext, string actionName) { //..code skip IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor); ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters); InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result); //.. } protected virtual IDictionary<string, object> GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor) { Dictionary<string, object> parametersDict = new Dictionary(StringComparer.OrdinalIgnoreCase); ParameterDescriptor[] parameterDescriptors = actionDescriptor.GetParameters(); foreach (ParameterDescriptor parameterDescriptor in parameterDescriptors) { parametersDict[parameterDescriptor.ParameterName] = GetParameterValue(controllerContext, parameterDescriptor); } return parametersDict; } protected virtual object GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) { // collect all of the necessary binding properties Type parameterType = parameterDescriptor.ParameterType; //you can register your binder, but default is DefaultModelBinder IModelBinder binder = GetModelBinder(parameterDescriptor); IDictionary<string, ValueProviderResult> valueProvider = controllerContext.Controller.ValueProvider; string parameterName = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName; Predicate<string> propertyFilter = GetPropertyFilter(parameterDescriptor); // finally, call into the binder ModelBindingContext bindingContext = new ModelBindingContext() { FallbackToEmptyPrefix = (parameterDescriptor.BindingInfo.Prefix == null), // only fall back if prefix not specified ModelName = parameterName, ModelState = controllerContext.Controller.ViewData.ModelState, ModelType = parameterType, PropertyFilter = propertyFilter, ValueProvider = valueProvider }; object result = binder.BindModel(controllerContext, bindingContext); return result; }
You can also register your ModelBinder to help mvc to build your parameter in the application startup like the code below.
public class ConferenceModelBinder : DefaultModelBinder { private readonly IConferenceRepository _repository; //We can require dependencies in binders just like normal classes public ConferenceModelBinder(IConferenceRepository repository) { _repository = repository; } public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { //first, we get the value from the request that matches the name ValueProviderResult providerResult = bindingContext.ValueProvider[bindingContext.ModelName]; //next we use our repository to find the Conference by the key Conference conference = _repository.GetByKey(providerResult.AttemptedValue); return conference; } } ModelBinders.Binders.Add(typeof (Conference), new ConferenceModelBinder( new ConferenceRepositoryStub()));
Nov 8, 2009
The options to render a partial view
Normally you can do this,
<% Html.RenderPartial("LogOnUserControl"); %>
Or you can do this
ViewResult partialResult = View("next"); return View(partialResult); <%ViewData.Model.ExecuteResult(ViewContext); %>
Nov 6, 2009
asp.net mvc request walkthrough
ASP.NET MVC request begin with a UrlRoutingModule. This component is not actually part of the System.Web.Mvc.dll, but of System.Web.Routing. You have to wire up in web.config like the following.
<httpModules> <add name="UrlRoutingModule" type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> </httpModules>
After that you can define your matching url pattern and the IRouteHandler which handle the matched url. You can add route like the following code.
Route route = new Route("{controller}/{action}/{id}", new MvcRouteHandler()) ; route.Defaults = new RouteValueDictionary( new { controller = "Home", action = "Index", id = "" }); RouteTable.Routes.Add("Default", route);
Of course, you can add the use the extension method define in System.Web.Mvc.RouteCollectionExtensions. You can add a route with your own IRouteHandler. The purpose of IRouteHandler is very simple, to create a IHttpHandler.
public class MvcRouteHandler : IRouteHandler { protected virtual IHttpHandler GetHttpHandler(RequestContext requestContext) { return new MvcHandler(requestContext); } #region IRouteHandler Members IHttpHandler IRouteHandler.GetHttpHandler(RequestContext requestContext) { return GetHttpHandler(requestContext); } #endregion }
The RequestContext object is interesting object.It contains a HttpContextBase object and a RouteData object.This object will be used through out the mvc stack. You can also change the mvc behavior by subclassing the MvcHandler.
protected virtual void ProcessRequest(HttpContext httpContext) { HttpContextBase iHttpContext = new HttpContextWrapper(httpContext); ProcessRequest(iHttpContext); } protected internal virtual void ProcessRequest(HttpContextBase httpContext) { AddVersionHeader(httpContext); // Get the controller type string controllerName = RequestContext.RouteData.GetRequiredString("controller"); // Instantiate the controller and call Execute IControllerFactory factory = ControllerBuilder.GetControllerFactory(); IController controller = factory.CreateController(RequestContext, controllerName); if (controller == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentUICulture, MvcResources.ControllerBuilder_FactoryReturnedNull, factory.GetType(), controllerName)); } try { controller.Execute(RequestContext); } finally { factory.ReleaseController(controller); } }
The IControllerFactory by default is DefaultControllerFactory. This can be changed in the application_startup method. The _factoryThunk is an Func
ControllerBuilder.Current.SetControllerFactory(new StructureMapControllerFactory()); public class ControllerBuilder { //.. other members public void SetControllerFactory(IControllerFactory controllerFactory) { if (controllerFactory == null) { throw new ArgumentNullException("controllerFactory"); } _factoryThunk = () => controllerFactory; } public void SetControllerFactory(Type controllerFactoryType) { if (controllerFactoryType == null) { throw new ArgumentNullException("controllerFactoryType"); } if (!typeof(IControllerFactory).IsAssignableFrom(controllerFactoryType)) { throw new ArgumentException( String.Format( CultureInfo.CurrentUICulture, MvcResources.ControllerBuilder_MissingIControllerFactory, controllerFactoryType), "controllerFactoryType"); } _factoryThunk = delegate() { try { return (IControllerFactory)Activator.CreateInstance(controllerFactoryType); } catch (Exception ex) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentUICulture, MvcResources.ControllerBuilder_ErrorCreatingControllerFactory, controllerFactoryType), ex); } }; } }
The most commonly used controller is System.Web.Mvc.Controller, which inherit ControllerBase class. It has a property ControllerContext, which holds a RequestContext object, and a reference the Controller itself. MvcHandler will call the controller's Execute(RequestContext) method, which calls ExecuteCore method. As a developer, you need to implement your action method, the ExecuteCore method will try to call you action method.
protected override void ExecuteCore() { TempData.Load(ControllerContext, TempDataProvider); try { string actionName = RouteData.GetRequiredString("action"); if (!ActionInvoker.InvokeAction(ControllerContext, actionName)) { HandleUnknownAction(actionName); } } finally { TempData.Save(ControllerContext, TempDataProvider); } } public IActionInvoker ActionInvoker { get { if (_actionInvoker == null) { _actionInvoker = new ControllerActionInvoker(); } return _actionInvoker; } set { _actionInvoker = value; } }
The ActionInvoker is also an extension point, you can set this property when the controller is created, you can do this in your ControllerFactory. By default, it is ControllerActionInvoker. Below is the code how ControllerActionInvoker select a action method, and invoking it. This is most interesting part.
// class ControllerActionInvoker 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"); } //this will return ReflectedControllerDescriptor, which is used to expose //some meta of the controller, which implement System.Reflection.ICustomAttributeProvider ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext); //Use controllerDescriptor build a ActionDescripter which is a ReflectedActionDescriptor, which also implements ICustomAttributeProvider ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName); if (actionDescriptor != null) { //FilterInfo has four list,ActionFilters(IActionFilter), AuthorizationFilters(IAuthorizationFilter), ExceptionFilters(IExceptionFilter), ResultFilters(IResultFilter), these list contains FilterAttribute, but also the controller itself, because Controller implements IActionFilter, IAuthorizationFilter, IExceptionFilter, IResultFilter FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor); try { AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor); if (authContext.Result != null) { // the auth filter signaled that we should let it short-circuit the request InvokeActionResult(controllerContext, authContext.Result); } else { if (controllerContext.Controller.ValidateRequest) { ValidateRequest(controllerContext.HttpContext.Request); } //parameters can build from controllerContext, and will be feed to actionMethod later. IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor); //your action method will be executed, and ActionResult will be //returned here, which normally is ViewResult ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters); //the ActionExecutedContext.Result will be examined, if it is ActionResult, it will be invoked to render a view InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result); } } catch (ThreadAbortException) { // This type of exception occurs as a result of Response.Redirect(), but we special-case so that // the filters don't see this as an error. throw; } catch (Exception ex) { // something blew up, so execute the exception filters ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex); if (!exceptionContext.ExceptionHandled) { throw; } InvokeActionResult(controllerContext, exceptionContext.Result); } return true; } // notify controller that no method matched return false; }
There are lots implementation details here, I am going to skip most of them here. And focus on how a view is rendered. When ActionResult is returned from the method call InvokeActionMethodWithFilters, it will be passed in the method InvokeActionResultWithFilters, that is the beginning of rendering a view. The following code will be eventually run.
//ControllerActionInvoker member protected virtual void InvokeActionResult(ControllerContext controllerContext, ActionResult actionResult) { actionResult.ExecuteResult(controllerContext); }
ViewResult inherit from ViewResultBase, which inherite ActionResult. The interesting method is ExecuteResult(ControllerContext), it basically to find a view and then render the view.
//ViewResultBase member public override void ExecuteResult(ControllerContext context) { if (context == null) { throw new ArgumentNullException("context"); } if (String.IsNullOrEmpty(ViewName)) { ViewName = context.RouteData.GetRequiredString("action"); } ViewEngineResult result = null; //View is property Of ViewResultBase, it is Type IView if (View == null) { result = FindView(context); View = result.View; } //push the ViewData, TempData into viewCotext, which is passed into View.Render() ViewContext viewContext = new ViewContext(context, View, ViewData, TempData); View.Render(viewContext, context.HttpContext.Response.Output); if (result != null) { result.ViewEngine.ReleaseView(context, View); } } public interface IView { void Render(ViewContext viewContext, TextWriter writer); } //ViewResult member protected override ViewEngineResult FindView(ControllerContext context) { //ViewEngineCollection ViewEngineResult result = ViewEngineCollection.FindView(context, ViewName, MasterName); if (result.View != null) { return result; } // we need to generate an exception containing all the locations we searched StringBuilder locationsText = new StringBuilder(); foreach (string location in result.SearchedLocations) { locationsText.AppendLine(); locationsText.Append(location); } throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, MvcResources.Common_ViewNotFound, ViewName, locationsText)); } //ViewResultBase member, this is an extension point //we can add our ViewEngine here. [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "This entire type is meant to be mutable.")] public ViewEngineCollection ViewEngineCollection { get { return _viewEngineCollection ?? ViewEngines.Engines; } set { _viewEngineCollection = value; } } public static class ViewEngines { private readonly static ViewEngineCollection _engines = new ViewEngineCollection { new WebFormViewEngine() }; public static ViewEngineCollection Engines { get { return _engines; } } }
We can see here, to find a view, we need a ViewEngineCollection. By default this ViewEngineCollection has one IViewEngin(WebFormViewEngine). The purpose a IViewEngine is to find partial view and view, like the following. But WebFormViewEngine inherit VirtualPathProviderViewEngine, which implmenet IViewEngine. To find View, first we need to locate the Physical path of the view file(aspx or ascx), to decide whether a file exist, there is to ask a buildManager to build an instance of it. Physical path searhing follow a couple of pattern, if the view file can not be located. Then an exeption will be throw. If found a the path will be cached for next probing.
public interface IViewEngine { ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName, bool useCache); ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache); void ReleaseView(ControllerContext controllerContext, IView view); } //VirtualPathProviderViewEngine member public virtual ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName, bool useCache) { if (controllerContext == null) { throw new ArgumentNullException("controllerContext"); } if (String.IsNullOrEmpty(viewName)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "viewName"); } string[] viewLocationsSearched; string[] masterLocationsSearched; string controllerName = controllerContext.RouteData.GetRequiredString("controller"); string viewPath = GetPath(controllerContext, ViewLocationFormats, "ViewLocationFormats", viewName, controllerName, _cacheKeyPrefix_View, useCache, out viewLocationsSearched); string masterPath = GetPath(controllerContext, MasterLocationFormats, "MasterLocationFormats", masterName, controllerName, _cacheKeyPrefix_Master, useCache, out masterLocationsSearched); if (String.IsNullOrEmpty(viewPath) || (String.IsNullOrEmpty(masterPath) && !String.IsNullOrEmpty(masterName))) { return new ViewEngineResult(viewLocationsSearched.Union(masterLocationsSearched)); } return new ViewEngineResult(CreateView(controllerContext, viewPath, masterPath), this); } //WebFormViewEngine constructor public WebFormViewEngine() { MasterLocationFormats = new[] { "~/Views/{1}/{0}.master", "~/Views/Shared/{0}.master" }; ViewLocationFormats = new[] { "~/Views/{1}/{0}.aspx", "~/Views/{1}/{0}.ascx", "~/Views/Shared/{0}.aspx", "~/Views/Shared/{0}.ascx" }; PartialViewLocationFormats = ViewLocationFormats; }
The WebFormViewEngine defines how to create view, it implement two abstract method of VirtualPathProviderViewEngine.
protected override IView CreatePartialView(ControllerContext controllerContext, string partialPath) { return new WebFormView(partialPath, null); } protected override IView CreateView(ControllerContext controllerContext, string viewPath, string masterPath) { return new WebFormView(viewPath, masterPath); }
The WebFormView use tranditional aspx ViewPage or ViewUserControl ascx to render view.
//WebFormView members public virtual void Render(ViewContext viewContext, TextWriter writer) { if (viewContext == null) { throw new ArgumentNullException("viewContext"); } //viewInstance is an instance of ViewPage(aspx or ascx) object viewInstance = BuildManager.CreateInstanceFromVirtualPath(ViewPath, typeof(object)); if (viewInstance == null) { throw new InvalidOperationException( String.Format( CultureInfo.CurrentUICulture, MvcResources.WebFormViewEngine_ViewCouldNotBeCreated, ViewPath)); } ViewPage viewPage = viewInstance as ViewPage; if (viewPage != null) { RenderViewPage(viewContext, viewPage); return; } //we can see that viewInstance does not necessary to be a ViewPage, it can be ViewUserControl as well ViewUserControl viewUserControl = viewInstance as ViewUserControl; if (viewUserControl != null) { RenderViewUserControl(viewContext, viewUserControl); return; } throw new InvalidOperationException( String.Format( CultureInfo.CurrentUICulture, MvcResources.WebFormViewEngine_WrongViewBase, ViewPath)); } private void RenderViewPage(ViewContext context, ViewPage page) { if (!String.IsNullOrEmpty(MasterPath)) { page.MasterLocation = MasterPath; } //passed in ViewData to aspx or ascx page.ViewData = context.ViewData; page.RenderView(context); } private void RenderViewUserControl(ViewContext context, ViewUserControl control) { if (!String.IsNullOrEmpty(MasterPath)) { throw new InvalidOperationException(MvcResources.WebFormViewEngine_UserControlCannotHaveMaster); } //passed in ViewData to aspx or ascx control.ViewData = context.ViewData; control.RenderView(context); }
To render a ViewPage, firstly, it initialized three very handy object will be used extensively in the aspx or ascx, they are Ajax(AjaxHelper), Html(HtmlHelper),Url(UrlHelper). Then it delegate the control to ProcessRequest method of Page class.
//public class ViewPage : Page, IViewDataContainer public virtual void RenderView(ViewContext viewContext) { ViewContext = viewContext; InitHelpers(); // Tracing requires Page IDs to be unique. ID = Guid.NewGuid().ToString(); ProcessRequest(HttpContext.Current); } public virtual void InitHelpers() { Ajax = new AjaxHelper(ViewContext, this); Html = new HtmlHelper(ViewContext, this); Url = new UrlHelper(ViewContext.RequestContext); }
HtmlHelper is responsible to render partial view.
internal virtual void RenderPartialInternal(string partialViewName, ViewDataDictionary viewData, object model, ViewEngineCollection viewEngineCollection) { if (String.IsNullOrEmpty(partialViewName)) { throw new ArgumentException(MvcResources.Common_NullOrEmpty, "partialViewName"); } //a new copy a ViewData is created ViewDataDictionary newViewData = null; if (model == null) { if (viewData == null) { newViewData = new ViewDataDictionary(ViewData); } else { newViewData = new ViewDataDictionary(viewData); } } else { if (viewData == null) { newViewData = new ViewDataDictionary(model); } else { newViewData = new ViewDataDictionary(viewData) { Model = model }; } } //a newViewContext is created, not shared with passed in data ViewContext newViewContext = new ViewContext(ViewContext, ViewContext.View, newViewData, ViewContext.TempData); //use the passed in viewEngineCollectio to newViewContext to find a partial view IView view = FindPartialView(newViewContext, partialViewName, viewEngineCollection); view.Render(newViewContext, ViewContext.HttpContext.Response.Output); } //ViewDataDictionary member [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "See note on SetModel() method.")] public ViewDataDictionary(ViewDataDictionary dictionary) { if (dictionary == null) { throw new ArgumentNullException("dictionary"); } foreach (var entry in dictionary) { _innerDictionary.Add(entry.Key, entry.Value); } foreach (var entry in dictionary.ModelState) { ModelState.Add(entry.Key, entry.Value); } Model = dictionary.Model; }
Nov 1, 2009
Semantics works?
A developer is a creativity entity. There are so many software technologies emerging everyday, many of us are struggling to keep pace with the them. I am just an average developer, and I still tirelessly studying these technologies, because programming is my passion and love. Gradually, I understand semantics is the driven force of the evolution of software tools, framework, platform, methodologies and standards. As a .net developer, I use the object-oriented programming, domain driven development, agile programing, mocking framework, asp.net mvc, css, xhtml, wpf, RESTful web service.
I truely believe that semantics works. Today I am moving my studying notes to SemanticsWorks.
Oct 23, 2009
Best practice to develop jQuery plugin
- Create a private scope for $
- Attach plugin to $.fn alias
- Add implicit iteration
- Enable chaining
- Add default options
- Add custom options
- global custom options
<div id="counter1"> </div> <div id="counter2"> </div> <script> (function($) { $.fn.count = function(customOptions){ var options = $.extend({},$.fn.count.defaultOptions, customOptions); return this.each(function(){ var $this = $(this); $this.text(options.startCount); var myInterval = window.setInterval(function(){ var currentCount = parseFloat($this.text()); var newCount = currentCount+1; $this.text(newCount+''); }, 1000); }); }; $.fn.count.defaultOptions = { startCount:'100' }; })(jQuery); jQuery.fn.count.defaultOptions.startCount = '300'; jQuery('#counter1').count(); jQuery('#counter2').count({startCount:'500'}); </script>
Oct 21, 2009
javascript reflection in jQuery
String: typeof object === "string" Number: typeof object === "number" Boolean: typeof object === "boolean" Object: typeof object === "object" Function: jQuery.isFunction(object) Array: jQuery.isArray(object) Element: object.nodeType null: object === null undefined: typeof variable === "undefined" or object.prop === undefined null or undefined: object == null toString = Object.prototype.toString, //var x = { name :"fred" }; //alert(x.hasOwnProperty("name")); hasOwn = Object.prototype.hasOwnProperty, push = Array.prototype.push, isFunction: function( obj ) { return toString.call(obj) === "[object Function]"; }, isArray: function( obj ) { return toString.call(obj) === "[object Array]"; }, isPlainObject: function( obj ) { // Must be an Object. // Because of IE, we also have to check the presence of the constructor property. // Make sure that DOM nodes and window objects don't pass through, as well if ( !obj || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval ) { return false; } // Not own constructor property must be Object if ( obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { return false; } // Own properties are enumerated firstly, so to speed up, // if last one is own, then all properties are own. var key; for ( key in obj ) {} return key === undefined || hasOwn.call( obj, key ); }, isEmptyObject: function( obj ) { for ( var name in obj ) { return false; } return true; }, isWindow: function( obj ) { return obj && typeof obj === "object" && "setInterval" in obj; }, //new in jQuery 1.4 $.type(object);
Oct 17, 2009
it must be "new"ed.
The following is constructor which prevent not using "new" keyword
function User(first, last){ if ( !(this instanceof arguments.callee) ) return new User(first, last); this.name = first + " " + last; }
Oct 16, 2009
javascript scope
object literal
var myObject = { name: "Jack B. Nimble", 'goto': 'Jail', grade: 'A', level: 3, "3": "three" }; alert(myObject.name); alert(myObject["name"]); alert(myObject["goto"]); alert(myObject.goto); //ok alert(myObject["3"]); //alert(myObject.3); //error
function overload
function.length can tell you the number of parameters defined, using this we can create overload functions
function addMethod(object, name, fn){ // Save a reference to the old method var old = object[ name ]; // Overwrite the method with our new one object[ name ] = function(){ // Check the number of incoming arguments, // compared to our overloaded function if ( fn.length == arguments.length ) // If there was a match, run the function return fn.apply( this, arguments ); // Otherwise, fallback to the old method else if ( typeof old === "function" ) return old.apply( this, arguments ); }; } function Ninjas(){ var ninjas = [ "Dean Edwards", "Sam Stephenson", "Alex Russell" ]; addMethod(this, "find", function(){ return ninjas; }); addMethod(this, "find", function(name){ var ret = []; for ( var i = 0; i < ninjas.length; i++ ) if ( ninjas[i].indexOf(name) == 0 ) ret.push( ninjas[i] ); return ret; }); addMethod(this, "find", function(first, last){ var ret = []; for ( var i = 0; i < ninjas.length; i++ ) if ( ninjas[i] == (first + " " + last) ) ret.push( ninjas[i] ); return ret; }); } var ninjas = new Ninjas(); assert( ninjas.find().length == 3, "Finds all ninjas" ); assert( ninjas.find("Sam").length == 1, "Finds ninjas by first name" ); assert( ninjas.find("Dean", "Edwards").length == 1, "Finds ninjas by first and last name" ); assert( ninjas.find("Alex", "X", "Russell") == null, "Does nothing" );
later method
Object.prototype.later = function (msec, method) { var context = this, args = Array.prototype.slice.apply(arguments, [2]); if (typeof method === 'string') { method = context[method]; } setTimeout(function () { method.apply(context, args); }, msec); return context; }; var o = { name: "fred", greet: function (msg) { alert("my name is " + this.name + "," + msg); } } o.later(1000, function () { alert("hello");}); o.later(1000, "greet", "how are you?");
fixed a closure bug
Closure refer the ability that a function can access and manipulate external variable from with a function. Sometimes, this is not good because the if a function depends on the external state. When the external state changes, the function may produce unexpected result.
//this is because the function inside setTimeout refers to the i only when it is //executed, by then i==4, the problem is that i is external variable for (var i = 0; i < 4; i++) { setTimeout(function() { alert(i); //it is always 4 }, i * 1000); } //the solution is make the i as local variable, so parameter is a solution, and //self-execution var count = 0; for (var i = 0; i < 4; i++) { (function(j) { setTimeout(function() { alert(j); //it will show 0, 1, 2, 3 }, j * 1000); }) (i); }
So sometimes it is good to remove the external dependencies. The follow code is anther example.
var ninja = { yell: function(n) { return n > 0 ? arguments.callee(n - 1) + "a" : "hiy"; } } /* this is also ok var ninja = { yell: function x(n) { return n > 0 ? x(n - 1) + "a" : "hiy"; } }; */ /*this is has bug, because ninja.yell is inside of function which depends external state var ninja = { yell: function(n) { return n > 0 ? ninja.yell(n - 1) + "a" : "hiy"; } }; */ var sumurai = { yell: ninja.yell }; ninja = null; ok(sumurai.yell(4) == "hiyaaaa", "argumnets.collee is the function itself");
efficency string operation
Because string is immutable in JavaScript, concatenation with array.join('') is much efficient. The following code use all the chinese characters. Click here to show
var sb = []; sb[sb.length] = ""; for (var i = 0x4e00; i <= 0x9fcf; i++) { sb[sb.length] = String.fromCharCode(i); } sb[sb.length] = "
"; $("#chinese").html(sb.join("")); return false;
the bind function
John Resig has page Learning Advanced JavaScript to explain how the following script works.
// The .bind method from Prototype.js Function.prototype.bind = function(){ var fn = this, args = Array.prototype.slice.call(arguments), object = args.shift(); return function(){ return fn.apply(object, args.concat(Array.prototype.slice.call(arguments))); }; };
His explanation is wonderful. And this piece of code is simple, powerful. But it maybe still hard for anyone to understand without any explanation. So I refactored it as follow, and add one use case.
Function.prototype.bind = function() { var function_to_be_bound = this; //convert argements into a real array var args = Array.prototype.slice.call(arguments); //the first element in the array is the context_object to be bound to var context_object = args.shift(); //the rest of elements in the array is the prefilled parameter var binding_parameters = args; return function() { var invoking_parameters = Array.prototype.slice.call(arguments); var combined_parameters = binding_parameters.concat(invoking_parameters); var result_from_function_run_in_new_context = function_to_be_bound.apply(context_object, combined_parameters); return result_from_function_run_in_new_context; }; } function reply_greeting(your_name) { //"this" is the context object alert("my name is " + this.name + ", Nice to meet you, " + your_name); } var fred = { name: "fred" }; var reply_greeting_of_fred_to_you = reply_greeting.bind(fred); var reply_greeting_of_fred_to_john = reply_greeting.bind(fred, "john"); reply_greeting_of_fred_to_you("jeff"); //expect: "my name is fred, Nice to meet you, jeff" reply_greeting_of_fred_to_john(); //expect: "my name is fred, Nice to meet you, john"
Another article may help you to understand is Functional Javascript
Oct 15, 2009
memoried function
Function.prototype.memorized = function(key) { this._values = this._values || {}; if (this._values[key] !== undefined) { return this._values[key] } else { //"this" is parent function object this._values[key] = this.apply(this, arguments); /* the "this" passed as context object is optional? */ return this._values[key]; } }; function isPrime(num) { alert(this); var prime = num != 1; for (var i = 2; i < num; i++) { if (num % i == 0) { prime = false; break; } } return prime; } var a = isPrime.memorized(5); alert(a); var b = isPrime.memorized(5); alert(b);
curry function
Function.method('curry', function() { //arguments can not be passed in to closure function //var l = arguments.length; //var args = []; //for (var i = 0; i < l; i++) { // args[i] = arguments[i]; //} var args = Array.prototype.slice.apply(arguments); var original_function = this; return function() { //arguments is not the external arguments //for (var i = 0; i < arguments.length; i++) { // args[args.length] = arguments[i]; //} args = args.concat(Array.prototype.slice.call(arguments)); return original_function.apply(null, args); }; }); function add() { var sum = 0; for (i = 0; i < arguments.length; i++) { sum += arguments[i]; } return sum; } var add1 = add.curry(1); var s = add1(2); alert(s);
Oct 13, 2009
Does it matter for an object to know its constructor?
We all knows that javascript inherit the functionality from its constructor. It is important to understand the concept every object has a secret link to constructor's prototype. How about constructor? Does a object know who is its constructor? Let's see a simple example
function Person() { alert(this.constructor.name); //Person this.constructor.count++; // reuse its constructor as a name space to prevent name polution }; Person.count = 0; var p = new Person(); alert(p.constructor == Person); //true alert(Person.count); //1
We know that constructor is also an object, (an object is not necessarily a constructor), in our case we reuse the constructor as namespace or object. And the Id is this last count. Here Person's prototype is created by Person function automatically which is essentially an {}, an empty object, so the reflection shows that Person is the constructor of p. However, if we explicitly assign a prototype to Person function. Something strange happens
function Person() { alert(this.constructor.name); //Person this.constructor.count++; // reuse its constructor as a name space to prevent name polution }; Person.count = 0; Object.count = 0; Person.prototype = {}; var p = new Person(); alert(p.constructor == Person); //false, it is Object alert(Person.count); //0 alert(Object.count); //1
Suddenly the reflection shows, the object became constructor. Why? We need to have deeper understand how constructor works. When an object is created by using "new Person()", the Person function finds that it does has no explicit prototype, then it create one explicitly, so the refection shows that Person is the constructor. But if Person finds it has an explicit prototype, it use that prototype, and the constructor of that prototype in our case is Object, reflection mechanism report that constructor is the constructor of newly created object. According to ECMAScript Language Specification Edition 3
ECMAScript supports prototype-based inheritance. Every constructor has an associated prototype,and every object created by that constructor has an implicit reference to the prototype (called the object’s prototype) associated with its constructor.
If we don't reuse constructor as name space like Person, this is does not cause any problem. If we explicitly use Person as name space, it also solves the problem.
function Person() { alert(Person.name); //Person Person.count++; };
However, using the syntax of p.constructor.xx give you more flexibility, we can solve the problem by just adding one line, it does not change other behaviors or add other functionality
Person.prototype.constructor = Person;
Sep 27, 2009
4 Equals, Reference Type, Value Type
Basically we have two kinds of comparison, identity comparison(whether two object has the same identity), semantic comparison(whether two object means the same thing, most people refer it as value equality comparison, I use "semantic" because value of reference type is a reference, even the values of reference typed variable are different, it is possible that they mean the same thing in semantics). Since we have the two different type, this makes things complicated. For example, can we compare the "value" of reference type, or can we compare the reference of value type. If there had been only reference type, if there had been no value type, the .net world will be simpler. Why we need two types? This is a deep question, lots of this topics has been covered in a book "CLR via C#". Basically, this a consideration of memory efficiency and performance. What we need to know is that the value of reference type is reference, the value of value type is value.
Reference type identity comparison
To do identity comparison for reference type, we should call Object.ReferenceEquals(objA, objB), or you can use shortcurt operator "==" like "objA == objB". The following source code shows that ReferenceEquals and == operator is the same.public class Object { [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] public static bool ReferenceEquals (Object objA, Object objB) { return objA == objB; } }If they are the same, why we still need ReferenceEquals, this is because "==" is an operator for object type, if we are not using this method, we can use "(object)a == (object)b".
//you can use TypeA a; TypeB b; Assert.IsTrue(ReferenceEquals(a, b) == ( (object)a == (object)b) );The "==" means different things for different type value type. What exactly "==" does? For all reference type and all primitive value type, like int, double, enum, it become "ceq" instruction after it is compiled msil. What does "ceq" do? It is clr implementation question, I guess it compare identity equal for reference type and compare value equal for primitive value type. But it means "==" operator for custom value type like struct, which has not default implementation.
Reference type semantic comparison
The default semantic comparison of reference type is identity comparison, because the value of reference type variable is a reference. The default implementation is as follow.// Returns a boolean indicating if the passed in object obj is // Equal to this. Equality is defined as object equality for reference // types and bitwise equality for value types using a loader trick to // replace Equals with EqualsValue for value types). // public virtual bool Equals(Object obj) { return InternalEquals(this, obj); } [MethodImplAttribute(MethodImplOptions.InternalCall)] internal static extern bool InternalEquals(Object objA, Object objB);According the comments, for reference type object, InternalEquals just compare the reference, it does not compare referenced content. The following code shows this behavior.
static void Main(string[] args) { Customer c1 = new Customer { Name = "fred" }; Customer c2 = new Customer { Name = "fred" }; Customer c3 = c1; Console.WriteLine(object.ReferenceEquals(c1, c2)); //False Console.WriteLine(object.ReferenceEquals(c1, c3)); //True Console.WriteLine(c1 == c2); //False Console.WriteLine(c1 == c3); //True Console.WriteLine(c1.Equals(c2)); //False, event the reference content is same Console.WriteLine(c1.Equals(c3)); //True } public class Customer { public string Name { get; set; } }But sometimes, we want to change this semantics. In our case, we can say if the name of customer is the same, regardless their identity. So we can override the instance Equals method like the following.
public class Customer { public string Name { get; set; } public override bool Equals(object obj) { var c = obj as Customer; if (c == null) { return false; } else { return this.Name == c.Name; } } }
Value type identity comparison
Can you compare identity of value type variable. "Yes". Should you compare identity of value types variable. "No". The result will always return "False", because object put in different boxes before comparison.Console.WriteLine(object.ReferenceEquals(1, 1)); // False
Value type semantic comparison
Although you can use "==" operator with primitive value type like System.Int32, but you can not use it with custom value type such as struct before you implement the operator by your self. But you can use object type's instance Equals to do semantic comparison, which use reflection to check content equality like below.public override bool Equals (Object obj) { BCLDebug.Perf(false, "ValueType::Equals is not fast. "+this.GetType().FullName+" should override Equals(Object)"); if (null==obj) { return false; } RuntimeType thisType = (RuntimeType)this.GetType(); RuntimeType thatType = (RuntimeType)obj.GetType(); if (thatType!=thisType) { return false; } Object thisObj = (Object)this; Object thisResult, thatResult; // if there are no GC references in this object we can avoid reflection // and do a fast memcmp if (CanCompareBits(this)) return FastEqualsCheck(thisObj, obj); FieldInfo[] thisFields = thisType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); for (int i=0; i<thisFields.Length; i++) { thisResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(thisObj,false); thatResult = ((RtFieldInfo)thisFields[i]).InternalGetValue(obj, false); if (thisResult == null) { if (thatResult != null) return false; } else if (!thisResult.Equals(thatResult)) { return false; } } return true; }Because the method use reflection to compare, it tends to be slow. So we should always override instance Equals() for your custom value type struct to improve performance.
Comparing objects of unknown type
If we don't know the types of two object, the best bet is to use static method object.Equals(objA, objB). This method check if the identity equal first, then check semantic equality, this if This method is as follow.public static bool Equals(Object objA, Object objB) { if (objA==objB) { return true; } if (objA==null || objB==null) { return false; } return objA.Equals(objB); }To wrap it, what does this means to me? We can follow the following pseudo code
if (we compare two object of the same type) { if (type is reference type) { if (we want semantic compare && we have override the objA.Eqauls method) { objA.Equals(B); } else //we just want to identity compare { always use "objA == objB"; but object.ReferneceEqual(objA, objB) and objA.Eqauls(objB) do the same thing in this case } } else //type is value type { if (we want identity compare) { forget about it, although we can call object.ReferenceEqual(objA, objB) it will always return false because of boxing } else //we should always use semantic compare { if (type is primitive value type like int) { x == y // it is compiled to ceq il instruction } else { if (you have implment the == operator for this type) { use objA == objB } else { use objA.Equels(objB) //if you want more efficent comparison override instece Equals method } } } } } else //we compare two object of unknown type { Object.Equals(objA, objB); }For reference type, "==" is enough for a situation, unless you want to change the default semantics comparison. For primitive value type, "==" is enough for most situations. For struct, you are encourage to override default semantics comparison obj.Equals() for performance, although not mandatory, and use obj.Equals for comparison.
Sep 26, 2009
IEnumberable, IQueryable , Lambda expression - part2
I have seen such following piece of code written by a developer from a client.
interface IContactRepository { IEnumberable<contact> GetSomeContacts(); } class ContactRepository : IContactRepository { public IEnumerable<contact> GetSomeContacts() { //query is linq to sql query object IQueryable<contact> query = ... return query; } }
Is it a better choice to using IEnumerable<t> instead of IQueryable<t>. I guess his concerns is that, if the interface is too specific, first this may give client more functionality than is required, second this may limit the server's choice of implementation. In lots case, this concern is right, we should give client the only functionality which client needs, nothing less and nothing more, and server should has more freedom to implement.
interface IPerson { void Eat(); void Sleep(); } interface ISales : IPerson { void Sell(); } interface ITeacher : IPerson { void Teache(); } class Service { //Unappropriate // public ISales GetPerson() // { // return ... // } //better public IPerson GetPerson() { return ... } }
Firstly, if the method return a ISales, First client will have one extra unnecessary method Sell. Secondly If the client only needs a IPerson, and the contract says client needs a IWorker, this will limit server's ability to serve the client, for example, server can not return a ITeacher.
Is this design guideline also applicable to the case of IContactRepository.
public interface IQueryable<t> : IEnumerable<t>, IQueryable, IEnumerable {} public interface IQueryable : IEnumerable { Type ElementType { get; } Expression Expression { get; } IQueryProvider Provider { get; } }
First the the IQuerable<t> interface does give user more functionality than the IEnunumerable<t>, but these members are read only, and client can not use them directly for query. Because the query functionality comes from the static method in Enumerable and Queryable, but not the IQuerable<t>, and IEnumeralbe<t>, from the client's perspective, Two interfaces works identically. Secondly, the interface does limit limit server's implementation choice, because server cannot return a IEnumberable<t> . Initially, I thought I can implement easily a empty IQueryable<t> that wrap a IEnumberable<t>. It turns out to be even easier. Because the Enumerable already implement an static method AsQueryable() for you, the Linq team in Microsoft already expect this is a common use case. So all you need to do is call the method can you IEnumberable&lgt;T> will become IQueryable<t>. like the following.
int[] intEnumerable = { 1, 2, 3 , 5}; IQueryable intQuery = intEnumerable.AsQueryable().Where( number => number > 2); foreach (var item in intQuery) { Console.WriteLine(item); } Console.WriteLine(intQuery.GetType().ToString()); //System.Linq.EnumerableQuery`1[System.Int32] //code decompiled by reflector ParameterExpression CS$0$0000; IQueryable intQuery = new int[] { 1, 2, 3, 5 }.AsQueryable<int>().Where<int>(Expression.Lambda<Func<int, bool>>(Expression.GreaterThan(CS$0$0000 = Expression.Parameter(typeof(int), "number"), Expression.Constant(2, typeof(int))), new ParameterExpression[] { CS$0$0000 })); foreach (object item in intQuery) { Console.WriteLine(item); } Console.WriteLine(intQuery.GetType().ToString());
So a it seems be a better to replace IEnumberable<t> with IQueryable<t>. As for as the interface concerns, the replacement does not give client any exactly same query experience and it is more difficult to implement. A great benefit of this replacement is the performance, using IEnumberable<t> will be much slower than IQuerable<t>. Consider the following code, the Where method for IQueryable<t> will treat the lambda expression as expression tree and query will be executed at server side which is much faster, while the IEnumerable<t> will treat the lambda expression as delegate and query will be executed at client side, which will be slower. Consider the following code.
var thisContact = contaceRepository. GetSomeContacts().Where( ctc => ctc.Id = 1).First();
Linq provide us a new way to design our domain model. In the post Extending the World, author says
Typically for a given problem, a programmer is accustomed to building up a solution until it finally meets the requirements. Now, it is possible to extend the world to meet the solution instead of solely just building up until we get to it. That library doesn't provide what you need, just extend the library to meet your needs.
It is very important to build extensible domain model by taking the advantage of IQueryable<t> interface. Using IEnumberable<t> only will hit the performance very seriously. The only pitfall to user IQueryable<t> is that user may send unnecessary complex to the server, but this can be resolved by designing the method so that only appropriate IQueryable<t> is returned, for example return GetSome instead of GetAll. Another solution is adding a view model which return a IEnumberable<t>
IEnumberable, IQueryable , Lambda expression - part1
When we type the following code
IEnumerable<int> intEnumerable = null; var q1 = intEnumerable.Where( x => x > 10);
we know that Where method is not part of the IEnumberable<t> interface or IEnumberable interface, it comes from extension method of Enumerable, which is static class and it has no inheritance relation With IEnumerable or IEnumberable<t>. The power of Linq-To-Object does not come from IEnumberable or IEnumberable<t> or its implemenation, it comes from the extension method. Let's take a look what does the extension method do? Using Reflector we get the following source code.
public static class Enumerable { public static IEnumerable<tsource> Where<tsource>(this IEnumerable<tsource> source, Func<TSource, bool> predicate) { if (source == null) { throw Error.ArgumentNull("source"); } if (predicate == null) { throw Error.ArgumentNull("predicate"); } if (source is Iterator<tsource>) { return ((Iterator<tsource>) source).Where(predicate); } if (source is TSource[]) { return new WhereArrayIterator<tsource>((TSource[]) source, predicate); } if (source is List<tsource>) { return new WhereListIterator<tsource>((List<tsource>) source, predicate); } return new WhereEnumerableIterator<tsource>(source, predicate); } }
We can see there , the delegate passed in to the method is the code that does the filtering.
IQueryable<t> inherit from IEnumerable. But what extra value does the IQueryable bring. Let's take a look of the following code. and the code it generated by c# compiler.
public interface IQueryable<t> : IEnumerable<t>, IQueryable, IEnumerable {} public interface IQueryable : IEnumerable { Type ElementType { get; } Expression Expression { get; } IQueryProvider Provider { get; } }
It does not tell too much? Let's move on an querable example and decomplie to see what it does.
IQueryable<int> intQuerable = null; var q2 = intQuerable.Where(x => x > 10); // decomplied by reflector ParameterExpression CS$0$0000; IQueryable<int> q2 = intQuerable.Where<int>(Expression.Lambda<Func<int, bool>>(Expression.GreaterThan(CS$0$0000 = Expression.Parameter(typeof(int), "x"), Expression.Constant(10, typeof(int))), new ParameterExpression[] { CS$0$0000 }));
From this example, we can see that the Lamda Expression is not converted to a delegate, but to an expression tree. But why the extension method Enumerable.Where(IEnumerable
public static IQueryable<tsource> Where<tsource>(this IQueryable<tsource> source, Expression<Func<TSource, bool>> predicate) { if (source == null) { throw Error.ArgumentNull("source"); } if (predicate == null) { throw Error.ArgumentNull("predicate"); } return source.Provider.CreateQuery<tsource>(Expression.Call(null, ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(new Type[] { typeof(TSource) }), new Expression[] { source.Expression, Expression.Quote(predicate) })); }
Unlike the "Where" method of the "Enumerable", this method does not have a delegate to do the filtering. And also the expression can not do the filtering either, it is the IQuerable.Provider which does the filtering. The provider takes the expression tree and does filtering later by converting expression tree to provider specific algorithm like TSQL.
IEumberable<t> is very easy to implement, in fact all the collection they are IEnumberable<T>. Iterator makes it even easier. So there is not such thing as implementing a IEnumberableProvider, because the delegate does the query. But to implement IQueryable is more difficult, because expression does not query. It is IQueryProvider does the job. You need to implement IQuerableProvider
public interface IQueryProvider { IQueryable CreateQuery(Expression expression); IQueryable<telement> CreateQuery<telement>(Expression expression); object Execute(Expression expression); TResult Execute<tresult>(Expression expression); }
Jul 28, 2009
Raise Event from outside
Normally raising event is the responsibility side of a class, but in workflow there is a need to raise event from outside. Here is the code that allow client dynamically raise event externally.
class EventRaiser { static BindingFlags getEventFlags = BindingFlags.Instance | BindingFlags.NonPublic; private object _eventObject; private MethodInfo _eventInvoker; public EventRaiser(object hostingObject, string eventName) { FieldInfo fieldInfo = hostingObject.GetType().GetField(eventName, getEventFlags); _eventObject = fieldInfo.GetValue(hostingObject); _eventInvoker = _eventObject.GetType().GetMethod("Invoke"); } public void RaiseEvent(WorkflowEventArguementBase argument) { _eventInvoker.Invoke(_eventObject, new object[] { null, argument }); } }
Jul 19, 2009
Disconnected Update with Entity Framework
In the first demo, we get disconnected entity, make change of it, and get copy of original copy from the database, and apply the changed entity to the original entity by using context.ApplyPropertyChanges method, and save it back to the database.
public void DemoDisconnectedUpdate1() { //using NoTracking to simulate the disconnected enviorment //or you can use context.Detach() to simulate that context.Contacts.MergeOption = MergeOption.NoTracking; var pendingContact = context.Contacts.Where(c => c.ContactID == 709).First(); //change pendingContact.FirstName = "somebody"; // ApplyChange1(pendingContact); } public void ApplyChange1(EntityObject pendingEntity) { context = new PEF(); context.GetObjectByKey(pendingEntity.EntityKey); context.ApplyPropertyChanges(pendingEntity.EntityKey.EntitySetName, pendingEntity); context.SaveChanges(); }
Unlike the first demo, in the second demo, we use a anonymous typed object to represent the change of the entity, and apply the change directly to the original version directly using reflection.
public void DemoDisconnectUpdate2() { EntityKey key = new EntityKey("PEF.Contacts", "ContactID", 709); var changes = new { FirstName = "xyz" }; UpdateEntity(key, changes); } public void UpdateEntity(EntityKey key, object changes) { var original = context.GetObjectByKey(key); ApplyChange(changes, original); context.SaveChanges(); } public void ApplyChange(object changes, object original) { Type newType = changes.GetType(); Type oldType = original.GetType(); var newProperties = newType.GetProperties(); foreach (var newProperty in newProperties) { var oldProperty = oldType.GetProperty(newProperty.Name); if (oldProperty != null) { oldProperty.SetValue(original, newProperty.GetValue(changes, null), null); } } }
Jul 16, 2009
Reference and EntityKey
When add an entity to your objectContext, if the entity reference an other existing entity, but that entity is not in memory, you need to create a EntityKey like the following.
var address = new Address(); address.City = "SomeCity"; address.AddressType = "Home"; address.ModifiedDate = DateTime.Now; address.ContactReference.EntityKey = new EntityKey("PEF.Contacts", "ContactID", 709); context.AddToAddresses(address); context.SaveChanges();
Jul 12, 2009
Naming in Entity Framework
When using the entity framework designer, you create you entity model with a naming convention. For example, a table "Customer" will map to a entity type "Customer" and entity set "CustomerSet" . It is very tempting to change the name of "CustomerSet" to to Customers. But what about Criterion, its plural forms is Criteria, what about Equipment, it is plural forms is also Equipment. I feel that the default naming convention is good enough, because it tells you it is a set and also my configuration is kept to minimum, isn't this the spirit of convention over configuraiton?
How ObjectContext manage entities
Those objects were created by an internal process called object materialization, which takes the returned data and builds the relevant objects for you. Depending on the query, these could be EntityObjects, anonymous types, or DbDataRecords. By default, for any EntityObjects that are materialized, the ObjectContext creates an extra object behind the scenes, called an ObjectStateEntry. It will use these ObjectStateEntry objects to keep track of any changes to their related entities. If you execute an additional query using the same context, more ObjectStateEntry objects will be created for any newly returned entities and the context will manage all of these as well. The context will keep track of its entries as long as it remains in memory. The ObjectContext can track only entities. It cannot keep track of anonymous types or nonentity data that is returned in a DbDataRecord.
ObjectStateEntry takes a snapshot of an entity's values as it is first created, and then stores the original values and the current values as two separate sets. ObjectStateEntry also has an EntityState property whose value reflects the state of the entity (Unchanged, Modified, Added, Deleted). As the user modifies the objects, the ObjectContext updates the current values of the related ObjectStateEntry as well as its EntityState.
The object itself also has an EntityState property. As long as the object is being managed by the context, its EntityState will always match the EntityState of the ObjectStateEntry. If the object is not being managed by the context, its state is Detached.
ObjectContext has a single method, SaveChanges, which persists back to the database all of the changes made to the entities. A call to SaveChanges will check for any ObjectStateEntry objects being managed by that context whose EntityState is not Unchanged, and then will use its details to build separate Insert, Update, and Delete commands to send to the database. ObjectContext can monitor the change of both entity and entity reference.
Pros and Cons of Load and Include
You have some things to consider when choosing between the Load and Include methods. Although the Load method may require additional round trips to the server, the Include method may result in a large amount of data being streamed back to the client application and then processed as the data is materialized into objects. This would be especially problematic if you are doing all of this work to retrieve related data that may never even be used. As is true with many choices in programming, this is a balancing act that you need to work out based on your particular scenario. The documentation also warns that using query paths with Include could result in very complex queries at the data store because of the possible need to use numerous joins. The more complex the model, the more potential there is for trouble.
You could certainly balance the pros and cons by combining the two methods. For example, you can load the customers and orders with Include and then pull in the order details on an as-needed basis with Load. The correct choice will most likely change on a case-by-case basis.
public static void DeferredLoadingEntityReference() { var addresses = from a in context.Addresses select a; foreach (var address in addresses) { if (address.CountryRegion == "UK") address.ContactReference.Load(); } } public static void EagerLoadWithInclude() { var test = from c in context.Contacts.Include("Addresses") where c.LastName == "Smith" select c; test.OuputTrace(); }
Debuging ObjectQuery
When you write query with ObjectQuery, it use IQueryable interface. Following extension function help your to debug ObjectQuery more easily.
public static class IQueryableExtenstion { public static ObjectQuery ToObjectQuery(this IQueryable query) { return query as ObjectQuery; } public static ObjectQuery<T> ToObjectQuery<T>(this IQueryable<T> query) { return query as ObjectQuery<T>; } public static string ToDatabaseSql(this IQueryable query) { try { return query.ToObjectQuery().ToTraceString(); } catch { return null; } } public static string ToEntitySql(this IQueryable query) { try { return query.ToObjectQuery().CommandText; } catch { return null; } } public static void OuputTrace(this IQueryable query) { Console.WriteLine(query.ToDatabaseSql()); Console.WriteLine(query.ToEntitySql()); } } //to use the extension function you can write the following code var test = from a in context.Addresses let c = new { a.Contact.FirstName, a.Contact.LastName, a.CountryRegion } group c by c.CountryRegion into mygroup where (mygroup.Count() > 150) select mygroup; test.OuputTrace();