Thursday, April 14, 2011

URL Routing Causing to Load Login.aspx and SyntaxError

It is very strange. After the URL Routing module had been added into the non-MVC Web application, the application kept trying to load Login.aspx into the pages that don't need authentication and cause other syntax errors.

Uncaught SyntaxError: Unexpected token <
Register.aspx:63Uncaught Error: ASP.NET Ajax client-side framework failed to load.
Login.aspx:3Uncaught SyntaxError: Unexpected token <
jsdebug:1Uncaught ReferenceError: Type is not defined
Uncaught SyntaxError: Unexpected token <
Login.aspx:187Uncaught ReferenceError: WebForm_AutoFocus is not defined

After a few hours searching in code, I realized that the problem was caused by WebResource.axd. All the scrips for WebForm validation, focus, post back and etc were replaced by Login.aspx. WebResource.axd were instead trying to load Login.aspx.

Another interesting is that I duplicated some of user controls and pages to another temporary project for investigation but I am unable to re-produce the same behavior. With the same routing table, the same Web.config and a scale-down Global.ascx, Login.aspx won't be loaded when it is not being asked for. All JavaScripts are loaded as expected by WebResource.axd without problems. I still wonder what settings or events in the original application cause this problem.

Since this problem is caused by routing, one way to fix is to bypass the route. Adding RouteTable.Routes.RouteExistingFiles = true; won't fix the problem. Instead, a specific rule for ignoring a route is needed.


// For .NET 4.0
//RouteTable.Routes.Ignore("{resource}.axd/{*pathInfo}");  

// For .NET 3.5 SP1
RouteTable.Routes.Add(new Route("{resource}.axd/{*pathInfo}", new StopRoutingHandler()));

Tuesday, April 12, 2011

DateTime.Parse problem

Some of DateTime are taken from the user input and they are instantiated by the simaple DateTime constructor new DataTime(year, month, day);. When they are serialized into XML, the string will become like this:

   2001-07-08T00:00:00   

I tried to parse it back to DateTime from a XML value string. Unfortunately, I tried every method I know but kept getting String was not recognized as a valid DateTime error. For example,

  
    // Every method here causes String was not recognized as a valid DateTime error.  
  
    DateTime d = DateTime.Parse(query.Element("joinDate").ToString());
    
    string dateStr = query.Element("joinDate").ToString().Replace("T", "");
    DateTime d = XmlConvert.ToDateTime(dateStr, "yyyy-mm-dd hh:mm:ss");
    
    string dateStr = query.Element("joinDate").ToString().Replace("T", "");
    DateTime d = DateTime.ParseExact(dateStr, "yyyy-mm-dd hh:mm:ss", null);  
    
    string dateStr = query.Element("joinDate").ToString().Replace("T00:00:00", "");
    DateTime d = DateTime.ParseExact(dateStr, "yyyy-mm-dd", null);   
    

None of the above work for me. I also tried to follow the example/best practice discussed in MSDN but the error won't go away. Timezone is not my concern. As a matter of fact, my serialized XML value is not really represented in UTC time ( 2001-07-08T00:00:00 ). I guess it may be why MSDN suggestion won't work!

I finally gave up trying and use Regular Expression to parse the date fields and then convert it to DateTime manually. The following is my solution. I am sure that there is a better way to handle it but I just don't know how and probably I don't understand how the DataTime.Parse works!

   
// Manually parse the string to DateTime

string pattern = @"(?\d{4})-(?\d{2})-(?\d{2})T00:00:00";
System.Text.RegularExpressions.Match m = 
System.Text.RegularExpressions.Regex.Match(query.Element("joinDate").ToString(), pattern);
int year = int.Parse(m.Groups["year"].ToString());
int month = int.Parse(m.Groups["mth"].ToString());
int day = int.Parse(m.Groups["day"].ToString());

// this statement is also exactly how 
// I create the DateTime from the user's input.
DateTime d = new DateTime(year, month, day); 

Read XML from MemoryStream

This illustration of this example follows the previous post. What if we want to generate XML into a stream instead of a physical file? Initially, I thought the solution was very simple and could be done in a minute. Instead, it took me hours to figure out.

    public MemoryStream ToXml() {
      MemoryStream ms = new MemoryStream();
      
      XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
      ns.Add("", "");    

      XmlSerializer ser = new XmlSerializer(typeof(Subscribers));      
      ser.Serialize(ms, new Subscribers(), ns);       
 
      return ms;
    } 

If you read the XML from the MemoryStream returned by the above code with XDocument or XmlDocument, you will encounter "Root element is missing" error. For example,

  using (StreamReader reader = new StreamReader(new Subscribers().ToXml())) {
    subscribers = XDocument.Load(reader);
  }

When the XmlSerializer has finished the "write" into the MemoryStream, the stream pointer will be at the end of the XML structure. Thus, the function should rewind the pointer to the beginning of the stream before returning the MemoryStream to the caller. Using ms.Seek(0, SeekOrigin.Begin); . before the statement return ms; will do the trick.

    public MemoryStream ToXml() {
      MemoryStream ms = new MemoryStream();
      
      XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
      ns.Add("", "");    

      XmlSerializer ser = new XmlSerializer(typeof(Subscribers));      
      ser.Serialize(ms, new Subscribers(), ns);       
      
      ms.Seek(0, SeekOrigin.Begin);  // rewind the pointer the top of the stream

      return ms;
    } 

Serialization: How to override the element Name of an item inside a Collection

We cannot control the display name for the element if the object is an item inside a collection / array / list and etc. We can use XmlAttributeOverrides to override each property name of that object but not the element name of the object itself.

Consider the following scenerio of the XML structure: <customers><customer></customer>...<customer></customer>...</customers>

  <?xml version="1.0" encoding="utf-8"?>  
  <customers>
    <customer id="2600CD00">
      <firstName>Pat</firstName>
      <lastName>Thurston</lastName>
      ...
    </customer>
    <customer id="1E9CC4B0">
      <firstName>Kari</firstName>
      <lastName>Furse</lastName>
      ...
    </customer>
    <customer id="60R120B3">
      <firstName>Carl</firstName>
      <lastName>Stuart</lastName> 
      ...
    </customer>
    ...  
  </customers>

We want to make use of the existing classes to generate the above XML structure.

BeforeAfter
  public class Subscriber {
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string ID { get; set; }
    ...
  }
 [XmlRoot(ElementName = "customer")]
  public class Subscriber {
    [XmlElement(ElementName="firstName")]
    public string FirstName { get; set; }

    [XmlElement(ElementName="lastName")]
    public string LastName { get; set; }

    [XmlAttribute( AttributeName="id" )]
    public string ID { get; set; }
    
    ...
  }
  public class Subscribers 
    : System.ComponentModel.IListSource {

    System.ComponentModel.BindingList<Subscriber> bList;
    ...
    
  }
public class Subscribers 
  : System.ComponentModel.IListSource {

  System.ComponentModel.BindingList<Subscriber> bList;
  ...
  
  public void ToXml(string outputFileName) {   
    StreamWriter w = new StreamWriter(outputFileName); 
    
    XmlRootAttribute root 
     = new XmlRootAttribute("customers");        
      
    XmlSerializerNamespaces ns 
     = new XmlSerializerNamespaces();
    ns.Add("", "");    
    
    XmlSerializer ser 
     = XmlSerializer(bList.GetType(), root)    
    ser.Serialize(w, bList, ns); 
  }    
}    

Now when we call Subscribers to generate XML: new Subscribers().ToXml("customers.xml");, the result is not what we want.

  <?xml version="1.0" encoding="utf-8"?>  
  <customers>
    <Subscriber id="2600CD00">
      <firstName>Pat</firstName>
      <lastName>Thurston</lastName>
      ...
    </Subscriber>
    <Subscriber id="1E9CC4B0">
      <firstName>Kari</firstName>
      <lastName>Furse</lastName>
      ...
    </Subscriber>
    <Subscriber id="60R120B3">
      <firstName>Carl</firstName>
      <lastName>Stuart</lastName> 
      ...
    </Subscriber>
    ...  
  </customers>

If we directly serialize our Subscriber class, for sure, we can alter the element name at the root level:

  <?xml version="1.0" encoding="utf-8"?>    
    <customer id="AE19600F">
      <firstName>Janko</firstName>
      <lastName>Cajhen</lastName>
      ...
    </customer>

Inspired by my another personal project, I fortunately figured out my own solution.

The Solution:

  [XmlRoot("customers")]
  public class Subscribers 
    : System.ComponentModel.IListSource {

    System.ComponentModel.BindingList<Subscriber> bList;
    ...
    
    
    [XmlElement("customer")]
    public List<Subscriber> All {
      get {
        return bList.ToList();
      }
    }
    
    ...
    
    public void ToXml(string outputFileName) {
      StreamWriter w = new StreamWriter(outputFileName); 
        
      XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
      ns.Add("", "");    

      XmlSerializer ser = new XmlSerializer(typeof(Subscribers));      
      ser.Serialize(w, new Subscribers(), ns); 
    }    
  }

Using DataPager in ListView

If the DataSource is not known statically at design time, the DataPager may not work correctly. The following error could be expected to happen when you click on the link provided by DataPager at the second time:

Failed to load viewstate. The control tree into which viewstate is being loaded must match the control tree that was used to save viewstate during the previous request. For example, when adding controls dynamically, the controls added during a post-back must match the type and position of the controls added during the initial request.

This problem occurs because the DataPager has no idea how to perform or calculate paging for you without knowing what page is supposed to display (i.e., StartIndex, and MaximuumRows in the page) when the DataSource is only known at runtime. Thus, you need to provide this missing piece of information to the DataPager before databinding.

Under Google search, you may find that quite a few people implemented the PreRender event of DataPager to perform databinding. Unfortunately, it doesn't work for this scenario. You can bind the data at DataPager's PreRender event but you are unable to supply paging properties to DataPager as mentioned above. Both StartRowIndex and MaximumRows properties are needed to set for paging before databinding. This problem took me a few hours to resolve. It turns out that the solution is very simple.

The Solution: You should add and implement the PagePropertiesChanging event of ListView. The PagePropertiesChangingEventArgs from the event argument will provide all your needy paging properties (StartRowIndex and MaximumRows) so that you can supply them to the DataPager.

    protected void ListView1_PagePropertiesChanging(object sender, PagePropertiesChangingEventArgs e) {     
      this.DataPage1.SetPageProperties(e.StartRowIndex, e.MaximumRows, false);
      BindData();  // set DataSource to ListView and call DataBind() of ListView
    }

If the DataPager is placed inside the ListView, do this:

    protected void ListView1_PagePropertiesChanging(object sender, PagePropertiesChangingEventArgs e) {
      ListView lv = sender as ListView;
      DataPager pager = lv.FindControl("DataPage1") as DataPager;
      pager.SetPageProperties(e.StartRowIndex, e.MaximumRows, false);
      BindData();  // set DataSource to ListView and call DataBind() of ListView
    }

Sunday, April 10, 2011

StructureMap Configuration and Object Creation

Reference: StructureMap - Scoping and Lifecycle Management
My Test version: StructureMap 2.6.1

Configuration for Object Creation

Recently I've used StructureMap in one of my projects and I begin to like it, especially I can control the object life cycle via StructureMap without changing my code. The followings are some ways how to configure StructureMap to create object instance.

  1. Per request basis

    StructureMap by default constructs object instance transiently. Thus, each time you will get a new instance.

        public void NewPerRequest() {
    
          Container mymap = new Container(x => {
            x.For<ISimple>().Use<Simple>();
          });
    
          ISimple first = mymap.GetInstance<ISimple>();
          ISimple second = mymap.GetInstance<ISimple>();
    
          string formatter = "PerRequest [default]: instances are the same? {0}";
          System.Console.WriteLine(formatter, ReferenceEquals(first, second));
        }
    
  2. Singleton

    StructureMap can inject code for you to apply Singleton design pattern if you want your object remains one and only one instance to be active during the life of the application.

        public void Singleton() {
          Container mymap = new Container(x => {
            x.For<ISimple>().Singleton().Use<Simple>();
          });
    
          ISimple first = mymap.GetInstance<ISimple>();
          ISimple second = mymap.GetInstance<ISimple>();
    
          string formatter =  "Singleton: instances are the same? {0}";
          System.Console.WriteLine(formatter, ReferenceEquals(first, second));
        }    
    
  3. HttpContextScoped

    You can configure StructureMap to contruct objects to live in HttpContext scope. This method only uses in Web application.

        public void HttpContextScoped() {
          Container mymap = new Container(x => {
            x.For<ISimple>().HttpContextScoped().Use<Simple>();
          });
    
          using (new MockHttpContext()) {
            ISimple first = mymap.GetInstance<ISimple>();
            ISimple second = mymap.GetInstance<ISimple>();
    
            string formatter = "HttpContextScoped: instances are the same: {0}";
            System.Console.WriteLine(formatter, ReferenceEquals(first, second));
          }
        }
    
  4. HttpContextLifecycle

    Configure StructureMap to contruct objects to live in HttpContext life cycle. This method only uses in Web application.

        public void HttpContextLifecycle() {
          Container mymap = new Container(x => {
            x.For<ISimple>().LifecycleIs(new HttpContextLifecycle()).Use<Simple>();
          });
    
          using (new MockHttpContext()) {
            ISimple first = mymap.GetInstance<ISimple>();
            ISimple second = mymap.GetInstance<ISimple>();
    
            string formatter = "HttpContextLifecycle: instances are the same: {0}";
            System.Console.WriteLine(formatter, ReferenceEquals(first, second));
          }
        }
    
  5. HttpSessionLifecycle

    Configure StructureMap to instantiate an object in HttpSession context. Like the previous two methods, it only works in regular Web environment.

        public void HttpSessionLifecycle() {
          Container mymap = new Container(x => {
            x.For<ISimple>().LifecycleIs(new HttpSessionLifecycle()).Use<Simple>();
          });
    
    
          #region Create HttpSession environment for test
          // Mock a new HttpContext by using SimpleWorkerRequest
          System.Web.Hosting.SimpleWorkerRequest request =
            new System.Web.Hosting.SimpleWorkerRequest("/", string.Empty, string.Empty, string.Empty, new System.IO.StringWriter());
    
          System.Web.HttpContext.Current = new System.Web.HttpContext(request);
    
          MockHttpSession mySession = new MockHttpSession(
              Guid.NewGuid().ToString(),
              new MockSessionObjectList(),
              new System.Web.HttpStaticObjectsCollection(),
              600,
              true,
              System.Web.HttpCookieMode.AutoDetect,
              System.Web.SessionState.SessionStateMode.Custom,
              false);
    
          System.Web.SessionState.SessionStateUtility.AddHttpSessionStateToContext(
             System.Web.HttpContext.Current, mySession
          );
          #endregion
    
          // now we are ready to test
          ISimple first = mymap.GetInstance<ISimple>();
          ISimple second = mymap.GetInstance<ISimple>();
    
          string formatter = "HttpSessionLifecycle: instances are the same: {0}";
          System.Console.WriteLine(formatter, ReferenceEquals(first, second));
    
        }
    
  6. HybridSessionLifecycle

    Those related to HttpContext or HttpSession are only used in ASP.NET Web environment. You won't be able to have your instance to run in client or service environment. HybridSessionLifecycle may be used to resolve it. For this method, HybridSessionLifecycle will try to use HttpContext storage if it exists; otherwise, it uses ThreadLocal storage. However, I found that this method somehow won't work with HttpHandler.

        // According to my experience, 
        // HybridSessionLifecycle works in most situations (including regular Web environment, 
        // Web service, console) but it doesn't work well with HttpHandler while 
        // HybridHttpOrThreadLocalStorage does. 
        public void HybridSessionLifecycle() {
          Container mymap = new Container(x => {
            x.For<ISimple>().LifecycleIs(new HybridSessionLifecycle()).Use<Simple>();
          });
    
    
          ISimple first = mymap.GetInstance<ISimple>();
          ISimple second = mymap.GetInstance<ISimple>();
      
          string formatter = "HttpSessionLifecycle: instances are the same: {0}";
          System.Console.WriteLine(formatter, ReferenceEquals(first, second));
        }
    
  7. HybridHttpOrThreadLocalScoped

    Like HybridSessionLifecycle, it works for most situations. It also work well in a HttpHandler.

       // Unlike HybridSessionLifecycle, it works well with HttpHandlers and WCF Services 
        // besides a regular ASP.NET environment.
        public void HybridHttpOrThreadLocalScoped() {
          Container mymap = new Container(x => {        
            x.For<ISimple>().HybridHttpOrThreadLocalScoped().Use<Simple>();
          });
    
          ISimple first = mymap.GetInstance<ISimple>();
          ISimple second = mymap.GetInstance<ISimple>();
    
          string formatter = "HybridHttpOrThreadLocalScoped: instances are the same: {0}";
          System.Console.WriteLine(formatter, ReferenceEquals(first, second));
        }
    
  8. UniquePerRequestLifecycle

    You can instruct StructureMap to create an unique instance per request in order to ensure no corrupted data and all states in fresh.

        public void UniquePerRequestLifecycle() {
          Container mymap = new Container(x => {
            x.For<ISimple>().LifecycleIs(new UniquePerRequestLifecycle()).Use<Simple>();
          });
    
          ISimple first = mymap.GetInstance<ISimple>();
          ISimple second = mymap.GetInstance<ISimple>();
    
          string formatter = "HttpContext Scope: instances are the same: {0}";
          System.Console.WriteLine(formatter, ReferenceEquals(first, second));
        }
    

The Code

  • NUnit Test

    using System;
    using StructureMapTest.Mocks;
    using StructureMap;
    using StructureMap.Pipeline;
    using StructureMap.Configuration.DSL;
    using NUnit.Framework;
    
    namespace StructureMapTest.UnitTest {
      [TestFixture]
      public class ReferenceEqualTest {
    
        private delegate NUnit.Framework.Constraints.SameAsConstraint CompareConstraintDelegate(object expected);
    
        private void Compare<T>(string formatter, CompareConstraintDelegate sameOrNot) where T : ILifecycle {
          Container mymap = new Container(x => {
            T t = Activator.CreateInstance<T>();
            x.For<ISimple>().LifecycleIs(t).Use<Simple>();
          });
    
          Compare(mymap, formatter, sameOrNot);
        }
    
        private void Compare(Container mymap, string formatter, CompareConstraintDelegate sameOrNot) {
          ISimple first = mymap.GetInstance<ISimple>();
          ISimple second = mymap.GetInstance<ISimple>();
    
          System.Console.WriteLine(formatter, ReferenceEquals(first, second));
    
          // Simulate to 2 situations:
          // 1. Assert.That(first, Is.SameAs(second));
          // 2. Assert.That(first, Is.Not.SameAs(second));
          Assert.That(first, sameOrNot(second));
        }
    
        [Test]
        public void Different_on_per_request_basis() {
    
          Container mymap = new Container(x => {
            x.For<ISimple>().Use<Simple>();
          });
    
          Compare(mymap, "PerRequest [default]: instances are the same? {0}", Is.Not.SameAs);
        }
    
        [Test]
        public void Same_on_Singleton_instance() {
          Container mymap = new Container(x => {
            x.For<ISimple>().Singleton().Use<Simple>();
          });
    
          Compare(mymap, "Singleton: instances are the same? {0}", Is.SameAs);
        }
    
        [Test]
        public void Same_on_HttpContextScoped() {
          Container mymap = new Container(x => {
            x.For<ISimple>().HttpContextScoped().Use<Simple>();
          });
    
          using (new MockHttpContext()) {
            Compare(mymap, "HttpContextScoped: instances are the same: {0}", Is.SameAs);
          }
        }
    
        [Test]
        public void Same_on_HttpContextLifecycle() {
          Container mymap = new Container(x => {
            x.For<ISimple>().LifecycleIs(new HttpContextLifecycle()).Use<Simple>();
          });
    
          using (new MockHttpContext()) {
            Compare(mymap, "HttpContextLifecycle: instances are the same: {0}", Is.SameAs);
          }
        }
    
        [Test]
        public void Same_on_HttpSessionLifecycle() {
          Container mymap = new Container(x => {
            x.For<ISimple>().LifecycleIs(new HttpSessionLifecycle()).Use<Simple>();
          });
    
          #region Create HttpSession environment for test
          // Mock a new HttpContext by using SimpleWorkerRequest
          System.Web.Hosting.SimpleWorkerRequest request =
            new System.Web.Hosting.SimpleWorkerRequest("/", string.Empty, string.Empty, string.Empty, new System.IO.StringWriter());
    
          System.Web.HttpContext.Current = new System.Web.HttpContext(request);
    
          MockHttpSession mySession = new MockHttpSession(
              Guid.NewGuid().ToString(),
              new MockSessionObjectList(),
              new System.Web.HttpStaticObjectsCollection(),
              600,
              true,
              System.Web.HttpCookieMode.AutoDetect,
              System.Web.SessionState.SessionStateMode.Custom,
              false);
    
          System.Web.SessionState.SessionStateUtility.AddHttpSessionStateToContext(
             System.Web.HttpContext.Current, mySession
          );
          #endregion
    
          // now we are ready to test
          Compare(mymap, "HttpSessionLifecycle: instances are the same: {0}", Is.SameAs);
        }
    
        // According to my experience, 
        // HybridSessionLifecycle works in most situations (including regular Web environment, 
        // Web service, console) but it doesn't work well with HttpHandler while 
        // HybridHttpOrThreadLocalStorage does. 
        [Test]
        public void Same_on_HybridSessionLifecycle() {
          Compare<HybridSessionLifecycle>("HybridSessionLifecycle: instances are the same: {0}", Is.SameAs);
        }
    
        // Unlike HybridSessionLifecycle, it works well with HttpHandlers and WCF Services 
        // besides a regular ASP.NET environment.
        [Test]
        public void Same_on_HybridHttpOrThreadLocalScoped() {
          Container mymap = new Container(x => {
            //x.For<ISimple>().HybridHttpOrThreadLocalScoped().Use<Simple>().Named("MyInstanceName");
            x.For<ISimple>().HybridHttpOrThreadLocalScoped().Use<Simple>();
          });
          Compare(mymap, "HybridHttpOrThreadLocalScoped: instances are the same: {0}", Is.SameAs);
        }
    
        [Test]
        public void Different_on_UniquePerRequestLifecycle() {
          Compare<UniquePerRequestLifecycle>("HttpContext Scope: instances are the same: {0}", Is.Not.SameAs);
        }
    
      }
    }
    
  • class Simple

    using System;
    
    namespace StructureMapTest.Mocks {
      public interface ISimple {
        void DoSomething();
      }
    
      public class Simple : ISimple {
        public void DoSomething() {
        }
      }
    }
    
  • class MockHttpContext

    using System;
    
    namespace StructureMapTest.Mocks {
      public class MockHttpContext : IDisposable {
        private readonly System.IO.StringWriter sw;
    
        public MockHttpContext() {
          var httpRequest = new System.Web.HttpRequest("notExisted.aspx", "http://localhost", string.Empty);
          sw = new System.IO.StringWriter();
          var httpResponse = new System.Web.HttpResponse(sw);
          System.Web.HttpContext.Current = new System.Web.HttpContext(httpRequest, httpResponse);
        }
    
        public void Dispose() {
          sw.Dispose();
          System.Web.HttpContext.Current = null;
        }
      }
    }
    
  • class MockHttpSession and class MockSessionObjectList

    using System;
    
    namespace StructureMapTest.Mocks {
      // This class follows the example at:
      // http://msdn.microsoft.com/en-us/library/system.web.sessionstate.ihttpsessionstate.aspx
      public sealed class MockHttpSession : System.Web.SessionState.IHttpSessionState {
        ...
      } 
    } 
    
    using System;
    
    namespace StructureMapTest.Mocks {
      // This class follows the example at 
      // http://msdn.microsoft.com/en-us/library/system.web.sessionstate.isessionstateitemcollection(v=VS.90).aspx
      public class MockSessionObjectList : System.Web.SessionState.ISessionStateItemCollection {
        ...
      } 
    }