.Net
Posted on: 7/22/2010 11:42:00 PM by Chris
To continue with the scheme of the last entry, we’re going to again depart from our originally planned series. Today we’re going to talk about our Blog Configuration. As we discussed in the initial database design entry, our blog config database table primarily consists of an XML column. Instead of creating a column for each setting we might want, we’ll store it all dynamically as XML. Furthermore, there is no way I’m going to think of all the config settings we’ll want while I’m writing this. Rather then trying to update this entry every time I think of something new, we’ll just outline what we’re going to do in our BlogConfig object so when we decide to add something else down the road, you’ll understand. First we’ll add a BlogConfig class to the Models folder in our DataLayer. This will be a partial class and will expand on our already created BlogConfig object (it was created as part of our Entity Framework EDMX):
1: namespace Veritas.DataLayer.Models
2: {3: public partial class BlogConfig
4: { 5: 6: } 7: }We’re probably going to end up with dozens of settings (at least) but we’re only going to handle three of them for now. We’ll add a boolean that will control if comments are allowed on the site, a string to contain the “about” information of the blog, and an int to store the number of posts:
1: namespace Veritas.DataLayer.Models
2: {3: public partial class BlogConfig
4: {5: public bool AllowComments { get; set; }
6: public string BlogAbout { get; set; }
7: public int PostCount { get; set; }
8: } 9: }Now, since we’re not making columns for any of these, we’re going to need to load them from the xml column whenever we load our BlogConfig object and pull them into the xml when we save it. Lastly we’ll add methods to load our xml from the properties and to load the properties from the xml:
1: public void LoadConfigFromXml()
2: {3: XmlDocument doc = new XmlDocument();
4: doc.LoadXml(this.ConfigXml);
5: if (doc.DocumentElement["AllowComments"] != null)
6: this.AllowComments = Convert.ToBoolean(doc.DocumentElement["AllowComments"].InnerText);
7: if (doc.DocumentElement["BlogAbout"] != null)
8: this.BlogAbout = doc.DocumentElement["BlogAbout"].InnerText;
9: if (doc.DocumentElement["PostCount"] != null)
10: this.PostCount = Convert.ToInt32(doc.DocumentElement["PostCount"].InnerText);
11: } 12: 13: public void BuildXmlFromConfig()
14: { 15: XElement blogConfigXml = 16: new XElement("BlogConfig",
17: new XElement("AllowComments", this.AllowComments),
18: new XElement("BlogAbout", this.BlogAbout),
19: new XElement("PostCount", this.PostCount)
20: ); 21: this.ConfigXml = blogConfigXml.ToString().Replace("\r\n ", "").Replace("\r\n", "");
22: }As we add (many) more config settings, we’ll have to alter these methods but you get the basic idea of how we’re storing and reading our configuration xml. I added in unit tests for both of these methods as well. You can get the latest here.
Comments: 3
|
|
Cutting.....wrists....
Stavos at 7/23/2010 9:26:28 AM
|
|
|
Sweet. Yours could be the first death fueled by blog boredom! I'll write about that and that will be my non-tech post. Need a rusty knife?
Chris at 7/23/2010 9:33:12 AM
|
|
|
Sure! Now if there was a blog to tell me to go either across the street or with traffic. I don't recall, and I don't want to fail again. Suicide is illegal you know!
Stavos at 7/23/2010 9:35:17 AM
|
Posted on: 7/12/2010 10:51:00 PM by Chris
If you were paying attention, you know that part 6 in our Veritas Blog Engine series was supposed to be about Error Logging. Well, we’re going to go a bit out of order and do Data Access first. The reason for this is that we’re going to use a lot of our data access methods in the sections I originally thought we’d write first. I ran into quite a bit of trouble the last time I played with Entity Framework when it was 3.5. Thankfully, they made quite a few improvements with 4.0 so we’re going to give it another try.
First things first, if you haven’t already done so, add a Models folder to your DataLayer project. All of our database objects and any extensions will sit in this folder and namespace. After that, we’ll add a “ADO .Net Entity Data Model” which is the Entity Framework file that we generate all of our database classes and connections in. Like every past version of Microsoft’s “This is the way to do database access”, we get to generate the majority of our code straight from the database. Included in this is the same functionality we had with the Linq2Sql generator for pluralizing / singularizing object names (though now it’s optional) as well as including foreign key columns in the models (which will hopefully fix the FK problems that plagued 3.5 (seriously it was like they designed it to be hard to have foreign key constraints). Once we’ve selected all of our tables and tell it to generate we’re given an EDMX file and presented with a fantastic database diagram view. Now, technically, we’ve generated our database access. We could call it a day. However, since we’re using the repository pattern and we want all of our data access methods to be in one place, we’ll go ahead and create all of those methods in the repository class we made in the last entry.
Before we can add any data access methods we need to add a instance of our Entities object (EDMX) to our repository like so:
private VeritasBlogDBV3Entities db = new VeritasBlogDBV3Entities();
So inside of our repository class we’ll use db. to do all our database interaction. The first method we’ll create is our Save method. This method handles saving any inserts, updates, and deletes after they’re done:
1: /// <summary>
2: /// Saves all DB changes and then accepts the changes.
3: /// </summary>
4: public void Save()
5: {
6: db.SaveChanges(System.Data.Objects.SaveOptions.AcceptAllChangesAfterSave);
7: }
We’ll call this method after doing any DB changes (or after any group of DB changes if we’re not doing them individually). After this, we’ll add entries (separated out into regions) for each of our objects for adding and deleting. So for example, here are the methods for the BlogConfig object:
1: #region BlogConfig
2:
3: public void Add(BlogConfig blogConfig)
4: {
5: db.BlogConfigs.AddObject(blogConfig);
6: }
7:
8: public void Delete(BlogConfig blogConfig)
9: {
10: db.BlogConfigs.DeleteObject(blogConfig);
11: }
12:
13: #endregion
These are pretty simple methods and just handle telling our entity object that we want to insert or delete something. The Save method still has to be called after each of these. A quick note on the delete methods: we may or may not end up ever using them. Typically I don’t like deleting data as much as “marking it inactive” for historical purposes. We’re going to write some unit tests in a second so we’re going to write a method to pull all our BlogConfigs from the DB. We won’t end up using this anywhere but in our unit test.
1: public IEnumerable<BlogConfig> GetAllBlogConfigs()
2: {
3: return db.BlogConfigs;
4: }
Now, before we can actually write some unit tests, we need to implement the StartTransaction and RollbackTransaction methods we made in the last entry. These methods will be called before and after any unit tests so we’re not actually putting anything in the database.
1: public DbTransaction Transaction { get; set; }
2: /// <summary>
3: /// Will create a new transation.
4: /// </summary>
5: public void StartTransaction()
6: {
7: db.Connection.Open();
8: DbTransaction trans = db.Connection.BeginTransaction();
9: this.Transaction = trans;
10: }
11:
12: /// <summary>
13: /// Rolls back a transation.
14: /// </summary>
15: public void RollbackTransaction()
16: {
17: this.Transaction.Rollback();
18: }
Now that these methods are implemented, we just need to make a base test class to handle calling these.
1: [TestClass()]
2: public class TestBase
3: {
4: public VeritasRepository repo = VeritasRepository.GetInstance();
5:
6: //Use TestInitialize to run code before running each test
7: [TestInitialize()]
8: public void MyTestInitialize()
9: {
10: repo.StartTransaction();
11: repo.Save();
12: }
13:
14: //Use TestCleanup to run code after each test has run
15: [TestCleanup()]
16: public void MyTestCleanup()
17: {
18: repo.RollbackTransaction();
19: }
20: }
Now we have everything we need to actually write a unit test to test adding a new BlogConfig:
1: /// <summary>
2: ///A test for Add BlogConfig
3: ///</summary>
4: [TestMethod()]
5: public void AddBlogConfigTest()
6: {
7: BlogConfig blogConfig = new BlogConfig()
8: {
9: Host = "test.com",
10: LastUpdateDate = DateTime.Now,
11: CreateDate = DateTime.Now,
12: ConfigXml = "<BlogConfig></BlogConfig>"
13: };
14: repo.Add(blogConfig);
15: repo.Save();
16: //Check the db for changes
17: var configs = repo.GetAllBlogConfigs().ToArray();
18: var testConfig = configs.Where(p => p.Host == "test.com").SingleOrDefault();
19: Assert.IsNotNull(testConfig);
20: }
Since this class implements our TestBase class, before this unit test is called, we’re getting a new instance of VeritasRepository and calling StartTransaction on it. When the test is done, it will call the RollbackTransaction method on the repo. If everything goes well, it will create a new config object, insert it into the database, save that change, pull it back, and make sure it comes back from the DB. Since most of our tests (such as our DeleteConfig test) will rely on having a BlogConfig and a BlogUser (if not many more things) to already be in the DB, we’ll eventually insert all of these in the test initialize but for now, we can add all of our Add / Delete methods and create tests for them. Since we need to be able to pull records back from the database to make sure our tests are working, we’ll need to add more methods like the GetAllBlogConfigs seen above. There are a lot of methods that we’ll in our repository that we’re not going to list here so if you want to see them all, download the files and check out the repo. As of now, we’ve got what should be all the data access methods we’ll need as well as the unit tests for all of them. As always, you can download the latest here.
Comments: 4
|
|
Where did all the fun go at this site? These coding blogs are making me suicidal!
Stavos at 7/14/2010 3:01:32 PM
|
|
|
Sorry Stavos. One day I'll stop sucking!
Chris at 7/20/2010 8:09:29 PM
|
|
|
You suck well, don't worry i'm hard on all my friends, I just want them to work hard, I really do enjoy your blogs/rants/storys.
Stavos at 7/20/2010 8:47:01 PM
|
|
|
Ok Steve, next post will be something ridiculous that has nothing to do with programming (hopefully).
Chris at 7/22/2010 9:04:50 PM
|
Posted on: 6/23/2010 11:29:00 PM by Chris
Now that we’ve established our database and our initial project structure, we can finally start coding. We’ll start our coding by creating some of the base objects we’re going to use throughout our application. I know, after 4 entries, can’t we actually make the site show something? We could but I don’t want to start showing stuff and then go back to refactor stuff if we can get some of it done ahead of time.
The first thing we’ll create is our repository class that' we’ll be using for Data Access. We won’t actually handle our data access quite yet, but we’re going to create our interface for accessing that data. We’ll add a new class to the DataLayer named “VeritasRepository”. For now, we’ll just implement the repository pattern inside this class:
1: public class VeritasRepository
2: {
3: internal const string CACHE_KEY = "_VeritasRepository_Cache_Key";
4:
5: private VeritasRepository() { }
6:
7: /// <summary>
8: /// Static method to get an instance of our Veritas Repostiory.
9: /// Checks to see if we have a context in case we're using
10: /// this in a unit test.
11: /// </summary>
12: /// <returns></returns>
13: public static VeritasRepository GetInstance()
14: {
15: if (HttpContext.Current == null)
16: return new VeritasRepository();
17:
18: if (HttpContext.Current.Items.Contains(CACHE_KEY))
19: return (VeritasRepository)HttpContext.Current.Items[CACHE_KEY];
20:
21: VeritasRepository repo = new VeritasRepository();
22: HttpContext.Current.Items[CACHE_KEY] = repo;
23: return repo;
24: }
25:
26: /// <summary>
27: /// Forces us to get a new instace for testing purposes
28: /// </summary>
29: /// <returns></returns>
30: public static VeritasRepository ForceNewInstance()
31: {
32: VeritasRepository repo = new VeritasRepository();
33: if (HttpContext.Current.Items.Contains(CACHE_KEY))
34: HttpContext.Current.Items[CACHE_KEY] = repo;
35: else
36: HttpContext.Current.Items.Add(CACHE_KEY, repo);
37: return repo;
38: }
39:
40: /// <summary>
41: /// Will create a new transation. Not implemented now but will be later.
42: /// </summary>
43: public void StartTransaction()
44: {
45: throw new NotImplementedException();
46: }
47:
48: /// <summary>
49: /// Rolls back a transation. Not implemented now but will be later.
50: /// </summary>
51: public void RollbackTransaction()
52: {
53: throw new NotImplementedException();
54: }
55: }
That’s pretty much it for our data layer for now. No we’ll do a couple things in our Business Layer. We’ll start by adding a new folder named “Screens”. To that folder we’ll add a new class named “ScreenBase”. For all of our views (to be created later) we’ll create a strongly typed Screen that we’ll tie to that view. Our ScreenBase is going to be an abstract class that will contain an instance of our repository class (so all of our child Screens will have access to it) and a few abstract properties and methods:
1: public abstract class ScreenBase
2: {
3: protected VeritasRepository repo = VeritasRepository.GetInstance();
4:
5: public ScreenBase() { }
6:
7: /// <summary>
8: /// Determines if the entites associated with the view
9: /// are valid or not.
10: /// </summary>
11: public abstract bool IsValid { get; }
12:
13: /// <summary>
14: /// Loads up whatever entities this screen may need.
15: /// </summary>
16: protected abstract void LoadScreen();
17: }
The last thing we’ll create today are some base objects in our UI project. Specifically we’ll create a Form class (to override some of the functionality of the html helpers) and some overrides of the ViewPages, ViewUserControl, and ViewMasterPage. We’ll start by adding a new class named “VeritasForm” to the Views folder:
1: public class VeritasForm
2: {
3: private VeritasRepository repo = VeritasRepository.GetInstance();
4: internal const string CACHE_KEY = "_VeritasForm_Cache_Key";
5: internal HtmlHelper Helper { get; set; }
6:
7: /// <summary>
8: /// Our static accessor so we can easily access this from the views.
9: /// </summary>
10: /// <param name="helper"></param>
11: /// <returns></returns>
12: public static VeritasForm GetInstance(HtmlHelper helper)
13: {
14: if (helper.ViewContext.HttpContext.Items.Contains(CACHE_KEY))
15: return (VeritasForm)helper.ViewContext.HttpContext.Items[CACHE_KEY];
16: VeritasForm form = new VeritasForm(helper);
17: helper.ViewContext.HttpContext.Items.Add(CACHE_KEY, form);
18: return form;
19: }
20:
21: private VeritasForm(HtmlHelper helper)
22: {
23: Helper = helper;
24: }
25: }
Later on we’ll put overrides on helper methods like ActionLink, DropdownList, and more here. Moving along, we’ll override the ViewMasterPage and create the “VeritasViewMasterPage”:
1: public class VeritasViewMasterPage : ViewMasterPage
2: {
3: public VeritasForm VeritasForm
4: {
5: get
6: {
7: return VeritasForm.GetInstance(Html);
8: }
9: }
10: }
We’re just adding the VeritasForm as a member of our overridden Master Page. The reason for this is so that later on when we have master pages, we’ll change what they inherit to the VeritasViewMasterPage and we’ll have easy access to VeritasForm. We’re going to do the same thing for the ViewPage and the ViewUserControl, though one thing to note is that for both of these, we have to override both the version that takes in a templated type and the version that does not.
1: public class VeritasViewPage<T> : ViewPage<T> where T : class
2: {
3: public VeritasForm VeritasForm
4: {
5: get
6: {
7: return VeritasForm.GetInstance(Html);
8: }
9: }
10: }
11:
12: public class VeritasViewPage : ViewPage
13: {
14: public VeritasForm VeritasForm
15: {
16: get
17: {
18: return VeritasForm.GetInstance(Html);
19: }
20: }
21: }
And here’s the VeritasViewUserControl:
1: public class VeritasViewUserControl<T> : ViewUserControl<T> where T : class
2: {
3: public VeritasForm VeritasForm
4: {
5: get
6: {
7: return VeritasForm.GetInstance(Html);
8: }
9: }
10: }
11:
12: public class VeritasViewUserControl : ViewUserControl
13: {
14: public VeritasForm VeritasForm
15: {
16: get
17: {
18: return VeritasForm.GetInstance(Html);
19: }
20: }
21: }
Comments: 0
<< Older posts

RSS (blog)