About two weeks ago I posted on how to Implement Custom Theme support in ASP.NET MVC. There were some breaking changes made when the Preview 5 release was released yesterday.
Here's a short list to a couple of the changes I had to make to my code from the previous post to get it working in ASP.NET MVC Preview 5:
- Delete WebFormThemeViewLocator - The contents of this object is now contained within the ViewEngine itself
- Delete WebFormThemeControllerFactory- This isn't needed anymore.
- Modify WebFormThemeViewEngine - Write a bunch of code that finds the appropriate View to use.
- Modify Global.asax - Remove code that adds the old ControllerFactory, and replace it with code that adds our newly improved WebFormThemeViewEngine
- Modify ControllerBase - Firstly, rename this to ThemeControllerBase since there is not a ControllerBase in System.Web.Mvc. Then, modify the code for the Execute method since it now takes in a RequestContext object as a parameter instead of a ControllerContext object.
Just for reference here's the code for the WebFormThemeViewEngine.
Download Code:
ASPNETMVC_Preview5_CustomThemeImplementation.zip (226.05 kb)
Below is the entire code for the WebFormThemeViewEngine, just for reference. If you are interested in looking at how I implemented this, just download and check out the entire code sample at the link above.
using System;
using System.Globalization;
using System.Linq;
using System.Web.Mvc;
public class WebFormThemeViewEngine : System.Web.Mvc.WebFormViewEngine
{
public WebFormThemeViewEngine()
{
base.ViewLocationFormats = new string[] {
"~/Views/{2}/{1}/{0}.aspx",
"~/Views/{2}/{1}/{0}.ascx",
"~/Views/{2}/Shared/{0}.aspx",
"~/Views/{2}/Shared/{0}.ascx"
};
base.MasterLocationFormats = new string[] {
"~/Views/{2}/{1}/{0}.master",
"~/Views/{2}/Shared/{0}.master"
};
base.PartialViewLocationFormats = new string[] {
"~/Views/{2}/{1}/{0}.aspx",
"~/Views/{2}/{1}/{0}.ascx",
"~/Views/{2}/Shared/{0}.aspx",
"~/Views/{2}/Shared/{0}.ascx"
};
}
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName, string masterName)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (string.IsNullOrEmpty(viewName))
{
throw new ArgumentException("Value is required.", "viewName");
}
string themeName = this.GetThemeToUse(controllerContext);
string[] searchedViewLocations;
string[] searchedMasterLocations;
string controllerName = controllerContext.RouteData.GetRequiredString("controller");
string viewPath = this.GetPath(this.ViewLocationFormats, viewName, controllerName, themeName, out searchedViewLocations);
string masterPath = this.GetPath(this.MasterLocationFormats, viewName, controllerName, themeName, out searchedMasterLocations);
if (!(string.IsNullOrEmpty(viewPath)) && (!(masterPath == string.Empty) || string.IsNullOrEmpty(masterName)))
{
return new ViewEngineResult(this.CreateView(controllerContext, viewPath, masterPath));
}
return new ViewEngineResult(searchedViewLocations.Union<string>(searchedMasterLocations));
}
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (string.IsNullOrEmpty(partialViewName))
{
throw new ArgumentException("Value is required.", partialViewName);
}
string themeName = this.GetThemeToUse(controllerContext);
string[] searchedLocations;
string controllerName = controllerContext.RouteData.GetRequiredString("controller");
string partialPath = this.GetPath(this.PartialViewLocationFormats, partialViewName, controllerName, themeName, out searchedLocations);
if (string.IsNullOrEmpty(partialPath))
{
return new ViewEngineResult(searchedLocations);
}
return new ViewEngineResult(this.CreatePartialView(controllerContext, partialPath));
}
private string GetThemeToUse(ControllerContext controllerContext)
{
string themeName = controllerContext.HttpContext.Items["themeName"] as string;
if (themeName == null) themeName = "Default";
return themeName;
}
private string GetPath(string[] locations, string viewName, string controllerName, string themeName, out string[] searchedLocations)
{
string path = null;
searchedLocations = new string[locations.Length];
for (int i = 0; i < locations.Length; i++)
{
path = string.Format(CultureInfo.InvariantCulture, locations[i], new object[] { viewName, controllerName, themeName });
if (this.VirtualPathProvider.FileExists(path))
{
searchedLocations = new string[0];
return path;
}
searchedLocations[i] = path;
}
return null;
}
}
Currently rated 3.7 by 3 people - Currently 3.666667/5 Stars.
- 1
- 2
- 3
- 4
- 5
Update 9/16/2008: After too long, I finally tried uninstalling SP1 and reinstalling it and that fixed the issue. I'm glad it's fixed now! I haven't seen any other suggestions for fixing this, and I first tried repairing the installation, but only an uninstall and reinstall fixed it. Hope this helps solves anyone elses headache with this issue.
A few months ago (back in February actually) I blogged showing how to use the new DataContractJsonSerializer to serialize your .NET objects to JSON. Everything was fine until .NET 3.5 SP1 was released. It appears that the .NET 3.5 SP1 update breaks the DataContractJsonSerializer. Using the exact same code from my previous post that worked perfect on .NET 3.5 RTM, breaks with the following exceptions in .NET 3.5 SP1 RTM:
First Exception
This exception occurrs when running this code within a Console application:
System.MissingFieldException was unhandled
Message="Field not found: 'System.Runtime.Serialization.XmlObjectSerializerContext.serializerKnownDataContracts'."
Source="System.ServiceModel.Web"
StackTrace:
at System.Runtime.Serialization.Json.XmlObjectSerializerWriteContextComplexJson..ctor(DataContractJsonSerializer serializer, DataContract rootTypeDataContract)
at System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph)
at System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph)
at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph)
at System.Runtime.Serialization.Json.DataContractJsonSerializer.WriteObject(XmlDictionaryWriter writer, Object graph)
at System.Runtime.Serialization.Json.DataContractJsonSerializer.WriteObject(Stream stream, Object graph)
at JsonSerializer_dotNet35SP1_IssueTest.JSONHelper.Serialize[T](T obj) in D:\TEST\JsonSerializer_dotNet35SP1_IssueTest\JsonSerializer_dotNet35SP1_IssueTest\Program.cs:line 56
at JsonSerializer_dotNet35SP1_IssueTest.Program.Main(String[] args) in D:\TEST\JsonSerializer_dotNet35SP1_IssueTest\JsonSerializer_dotNet35SP1_IssueTest\Program.cs:line 22
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
Code for Console app:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization;
//using System.ServiceModel.Web;
namespace JsonSerializer_dotNet35SP1_IssueTest
{
public class Program
{
static void Main(string[] args)
{
/// Sample code using the above helper methods
/// to serialize and deserialize the Person object
Person myPerson = new Person("Chris", "Pietschmann", 26);
// Serialize
string json = JSONHelper.Serialize<Person>(myPerson);
// Deserialize
myPerson = JSONHelper.Deserialize<Person>(json);
}
}
[DataContract]
public class Person
{
public Person() { }
public Person(string firstname, string lastname, int age)
{
this.FirstName = firstname;
this.LastName = lastname;
this.Age = age;
}
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
[DataMember]
public int Age { get; set; }
}
public class JSONHelper
{
public static string Serialize<T>(T obj)
{
System.Runtime.Serialization.Json.DataContractJsonSerializer serializer
= new
System.Runtime.Serialization.Json.DataContractJsonSerializer(obj.GetType());
MemoryStream ms = new MemoryStream();
serializer.WriteObject(ms, obj);
string retVal = Encoding.Default.GetString(ms.ToArray());
return retVal;
}
public static T Deserialize<T>(string json)
{
T obj = Activator.CreateInstance<T>();
MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json));
System.Runtime.Serialization.Json.DataContractJsonSerializer serializer
= new
System.Runtime.Serialization.Json.DataContractJsonSerializer(obj.GetType());
obj = (T)serializer.ReadObject(ms);
ms.Close();
return obj;
}
}
}
Second Exception
This exception occurrs when running this code within an ASP.NET WebPage:
System.MissingMethodException was unhandled by user code
Message="Method not found: 'Boolean System.Runtime.Serialization.DataContract.get_IsReference()'."
Source="System.ServiceModel.Web"
StackTrace:
at System.Runtime.Serialization.Json.DataContractJsonSerializer.CheckIfTypeIsReference(DataContract dataContract)
at System.Runtime.Serialization.Json.DataContractJsonSerializer.get_RootContract()
at System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalWriteObjectContent(XmlWriterDelegator writer, Object graph)
at System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalWriteObject(XmlWriterDelegator writer, Object graph)
at System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExceptions(XmlWriterDelegator writer, Object graph)
at System.Runtime.Serialization.Json.DataContractJsonSerializer.WriteObject(XmlDictionaryWriter writer, Object graph)
at System.Runtime.Serialization.Json.DataContractJsonSerializer.WriteObject(Stream stream, Object graph)
at _Default.JSONHelper.Serialize[T](T obj) in d:\TEST\JsonSerializer_dotNet35SP1_IssueTest_ASPNET\Default.aspx.cs:line 55
at _Default.Page_Load(Object sender, EventArgs e) in d:\TEST\JsonSerializer_dotNet35SP1_IssueTest_ASPNET\Default.aspx.cs:line 22
at System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e)
at System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e)
at System.Web.UI.Control.OnLoad(EventArgs e)
at System.Web.UI.Control.LoadRecursive()
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
InnerException:
Code for ASP.NET app:
using System;
using System.IO;
using System.Runtime.Serialization;
using System.Text;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
/// Sample code using the above helper methods
/// to serialize and deserialize the Person object
Person myPerson = new Person("Chris", "Pietschmann", 26);
// Serialize
string json = JSONHelper.Serialize<Person>(myPerson);
// Deserialize
myPerson = JSONHelper.Deserialize<Person>(json);
}
[DataContract]
public class Person
{
public Person() { }
public Person(string firstname, string lastname, int age)
{
this.FirstName = firstname;
this.LastName = lastname;
this.Age = age;
}
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
[DataMember]
public int Age { get; set; }
}
public class JSONHelper
{
public static string Serialize<T>(T obj)
{
System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(obj.GetType());
MemoryStream ms = new MemoryStream();
serializer.WriteObject(ms, obj);
string retVal = Encoding.Default.GetString(ms.ToArray());
return retVal;
}
public static T Deserialize<T>(string json)
{
T obj = Activator.CreateInstance<T>();
MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json));
System.Runtime.Serialization.Json.DataContractJsonSerializer serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(obj.GetType());
obj = (T)serializer.ReadObject(ms);
ms.Close();
return obj;
}
}
}
WTF??
I searched for a possible solution, but the only thing I found was this link (http://forums.msdn.microsoft.com/en-US/csharpgeneral/thread/37558154-88a9-41f0-a9d6-a2cb4052a5ce/) where someone mentions the First Exception above. The suggested solution was to use the KnownType Attribute on the object, but that doesn't help with my above code.
Does anyone know a work around for this that doesn't involve using the older JavaScriptSerializer object?
Well, for now, it looks like I'm almost the only one to experience this issue.
Be the first to rate this post - Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5
I just listened to the latest episode of FLOSS Weekly; episode 36 on CouchDB. This episode is an interview with Jan Lehnardt about CouchBD.
Here's a description of what CouchDB is from their website:
Apache CouchDB is a distributed, fault-tolerant and schema-free
document-oriented database accessible via a RESTful HTTP/JSON API. Among other
features, it provides robust, incremental replication with bi-directional
conflict detection and resolution, and is queryable and indexable using a
table-oriented view engine with JavaScript acting as the default view
definition language.
CouchDB is written in Erlang, but can be easily accessed from any
environment that provides means to make HTTP requests. There are a multitude of
third-party client libraries that make this even easier for a variety of
programming languages and environments.
I haven't installed or tested it out, but it looks like a really cool idea for a small efficient database design. I always find it interesting to hear about some of these cool "new" projects that look at everyday problems in new and different ways.
Be the first to rate this post - Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5
Update 8/29/2008: I posted a new blog post today that contains updated code for this example that works with the newly released ASP.NET MVC Preview 5. The new code is located here.
One option to do theming in ASP.NET MVC is to use the standard ASP.NET Theme functionality (App_Themes folder glory and all), but it just doesn't seem complete. Also, by doing that you don't get to generate completely custom HTML specific to each Theme. After all, the beauty of ASP.NET MVC is being able to completely control the HTML output of the application. So, I played around with things a bit and figured out how to create a custom ViewEngine that will allow you to create a seperate sub-folder within the Views folder for each Theme. This way you can have a completely different version of each View for each Theme, and maintain complete control over the HTML output on a per Theme basis.
I used the ASP.NET MVC Preview 4 release to write this article.
Create Themed Views
First, we'll make some changes to the Views contained within the default ASP.NET MVC Template.
Here's a brief summary of what changes are needed:
- Create a sub-folder named "Default" within the Views folder, and paste all the files and folders within the Views folder into this folder. This will be out "Default" theme.
- Cut the Site.css file from the Content folder, and paste it into the Shared folder within the newly created "Default" theme folder.
- Modify the CSS link reference in the "Shared/Site.Master" file within the "Default" theme to reference the new location for the Site.css file.
- Modey all the .aspx View pages to reference the new location of the MasterPage file.
Once, the above changes are made we have our first "Default" theme created. Now to create a new theme, just create a copy of this folder and make sure to update all the references within the files to reference the theme folder the files are contained within.
To the right is a screenshot displaying the layout of the Theme folders as described above.
In the code example attached to this article, I created a second theme
named "Red" and I changed the background color of the Site.css file to
the color Red.
Create a Custom ViewEngine
I found the following article usefull and very descriptive on creating custom ViewEngines, so I'm not going to re-write it. Just refer to the following link on how to create a custom ViewEngine in ASP.NET MVC.
http://blog.maartenballiauw.be/post/2008/05/20/Creating-a-custom-ViewEngine-for-the-ASPNET-MVC-framework.aspx
Instead, I'll just list the code I wrote for setting up Themes and give a brief description of what it does.
First, we need to create a ViewLocator object that will contain VirtualPath references that describe to ASP.NET MVC how to find a specified View. To make things simpler I'm just inheriting from the WebFormViewLocator so I don't have to re-implement it's entire logic. Also, one thing to note is the constructor of our WebFormThemeViewLocator object takes a string argument that is the name of the Theme it is to describe the VirtualPath references for.
public class WebFormThemeViewLocator : System.Web.Mvc.WebFormViewLocator
{
public WebFormThemeViewLocator(string themeName)
{
base.ViewLocationFormats = new string[] {
"~/Views/" + themeName + "/{1}/{0}.aspx",
"~/Views/" + themeName + "/{1}/{0}.ascx",
"~/Views/" + themeName + "/Shared/{0}.aspx",
"~/Views/" + themeName + "/Shared/{0}.ascx"
};
base.MasterLocationFormats = new string[] {
"~/Views/" + themeName + "/{1}/{0}.master",
"~/Views/" + themeName + "/Shared/{0}.master"
};
}
}
Next, we need to create a custom ViewEngine. Also, in this case, I'm inheriting the WebFormViewEngine so I don't have to re-implement it's entire logic. This WebFormThemeViewEngine just overrides the RenderView method so that it can get the Theme to display from the HttpContext.Items collection and implement the new WebFormThemeViewLocator instead of the default WebFormViewLocator.
public class WebFormThemeViewEngine : System.Web.Mvc.WebFormViewEngine
{
protected override void RenderView(System.Web.Mvc.ViewContext viewContext)
{
string themeName = viewContext.HttpContext.Items["themeName"] as string;
if (themeName == null) themeName = "Default";
base.ViewLocator = new WebFormThemeViewLocator(themeName);
base.RenderView(viewContext);
}
}
Next, we need to create a custom ControllerFactory that we'll setup ASP.NET MVC to use so we can use our custom ViewEngine. Again, I'm inheriting the DefaultControllerFactory so I only have to add the necessary logic to use the custom ViewEngine so I don't have to re-implement it's entire logic.
public class WebFormThemeControllerFactory : System.Web.Mvc.DefaultControllerFactory
{
protected override System.Web.Mvc.IController CreateController(System.Web.Routing.RequestContext requestContext, string controllerName)
{
System.Web.Mvc.Controller controller = base.CreateController(requestContext, controllerName) as System.Web.Mvc.Controller;
controller.ViewEngine = new WebFormThemeViewEngine();
return controller;
}
}
Finally, to tell ASP.NET MVC to use the WebFormThemeControllerFactory object, and ultimately our custom ViewEngine, the following line of code needs to be added to the Application_Start event handler:
ControllerBuilder.Current.SetControllerFactory(typeof(WebFormThemeControllerFactory));
Dynamically Determine Which Theme to Display to the User
The way I've set things up in the above code for the custom ViewEngine, I've wired it up so that you can set the Theme to display anywhere within your Controller. This gives the most flexibility I could think of to set the Theme since you'll most likely need to access a database of some kind to determine which Theme to display, and this way you can do that before, after or at the same time you get the data to display.
Here's an example of how to set the Theme to "Red", and you can do this anywhere within one of your Action methods of your Controller.
controllerContext.HttpContext.Items["themeName"]
The reason I'm storing the Theme Name using HttpContext.Items is so that we can access the Theme Name from a single place within both the custom ViewEngine and the View itself.
Also, the code example attached to this article uses a ControllerBase class that each of the Controllers inherit so it's more resuable, and it also grabs the Theme to display from the QueryString for simplicity.
Sharing UI Elements Between Themes
One thing that is done alot is sharing certain UI elements between Themes that are similar or almost identical. This can be done by creating user controls within a seperate folder than the View folder, and using/referencing them within the Pages/Views (.aspx) or Controls (.ascx) within the Themes. This way you can create shared UI elements that can be shared across multiple Themes.
In the code attached to this article, I've created a "~/Controls" folder and placed in it a ThemeLinks.ascx control that is used by both the "Default" and "Red" themes to display links (as tabs in the top navigation bar) for you to select which Theme to see.
Download the Code
Ok, after reading all the above, I'm sure you're probably a little confuses since I only gave a brief overview. Well, here's the code. I started with an ASP.NET MVC Website (as described in one of my previous posts) using the Preview 4 release and made all the changes described above. Have fun, and please post any comments you have on this, or any suggestions on making it better.
Download Code: ASPNETMVC_CustomThemeImplementation.zip (207.98 kb)
Currently rated 5.0 by 2 people - Currently 5/5 Stars.
- 1
- 2
- 3
- 4
- 5
I recently started getting to know the ASP.NET MVC Framework (more specifically the Preview 4 release). The first thing I noticed upon installation is that it only comes with a Web Application Project Template. There is no regular ASP.NET Website template. So, I decided to try converting the Web Application Project Template to a regular ASP.NET Website. I actually prefer to use Website projects instead of Web Application projects since it gives a little more flexibility to deploy changes to the website after the website is already live. Plus, you can still throw anything you want into a DLL that goes into the Bin if you want.
I only needed to make a couple small changes when copying over the code into a new ASP.NET Website:
- Convert the .aspx pages from using CodeBehind to CodeFile.
- Place the Controllers folder into the App_Code.
- Change the Global.asax to be ASP.NET Website friendly (aka Not compile into DLL like in Web Application Project)
That's pretty much it, now I have a nice ASP.NET MVC Website template that I can use. To the right is an image of the file layout of the new ASP.NET MVC Website project.
The ASP.NET MVC release I used for this was the ASP.NET MVC Preview 4 release available on CodePlex.
Also, here's a link to download the code: ASP.NET MVC_Preview4_Website Project.zip (192.03 kb)
Currently rated 5.0 by 3 people - Currently 5/5 Stars.
- 1
- 2
- 3
- 4
- 5
Update: Ok, my email is back and working again.
FYI, for anyone trying to email me, my email is currently down, so I wont see your messages until it's back up. The issue is actually GMail. I use GMail Apps for your domain to host my email (both pietschsoft.com and simplovation.com) email address's, and currently their service is down. I don't know if everyones is out, but mine is. I'll post an update when it's back, and reply to all emails as soon as I can too. This is actually the first time GMail has been down for me with all my email address's.
Currently rated 5.0 by 1 people - Currently 5/5 Stars.
- 1
- 2
- 3
- 4
- 5
Yesterday, Microsoft released the final RTM release of SQL Server 2008. Finally, the long anticipated release of SQL 2008 is here. Unfortunately, the Express edition of SQL 2008 isn't available yet (and according to the previous link, it wont be until the end of August), but if you have an MSDN Subscription you can download any of the other editions today.
I'm currently installing SQL 2008 Developer Edition, and before the installation could proceed, it needed to install an update to the .NET Framework 3.5. What update does it need to install? Well, .NET 3.5 Service Pack 1 is the update it needs. Now as you can see from the below screenshot, it doesn't say anything about being Beta. I wonder?... I wouldn't think SQL 2008 RTM would install .NET 3.5 SP1 Beta... So, I could only assume that this is the .NET 3.5 SP1 RTM that it's installing. However, this is a presumptuous conclusion and I don't really have any facts to back this up. It does make you wonder though...
Be the first to rate this post - Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5
Have you got a cool piece of code you wrote, but don't know what to do with?
Have you got a cool idea, but need the motivation to work on it?
Would you like an MSDN Subscription, but don't have the money to pay for it?
If you can answer, "YES" to any of the above questions, then you should definitely submit an entry in the Community Coding Contest by midnight September 30, 2008!
As of today, there are 58 days left to submit your entries in the Community Coding Contest. There are more prizes than just two MSDN Premium Subscriptions with Visual Studio Team System 2008 Suite being given away; a few companies have donated additional prizes too! View the Prizes page for a full list of the prizes being given away. Also, if we get a ton of entries, we may be able to get more prizes donated!
Once the entry phase of the contest is finished, the website will be opened up for the community to vote on the winners. We aren't going to have a panel of judges that might give the prizes to people they know. YOU the community are going to be in charge to judging the contest, on any criteria you want, to decide who wins these awesome prizes!
I'm anxious to see what cool intries get submitted over the next two months!
More Info: http://communitycodingcontest.org
Be the first to rate this post - Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5
|