EPiServer’s ObjectStore (Part 2)

I think we’re mostly finished investigating ObjectStore for now. In this article I’ll try to finish up on the apsects of using the Object store in a real life solution that is a basic Page comments. In my previous article concerning ObjectStore I have described a way of storing and retrieving an object from the Store, which is fine and dandy if we know exactly the object’s OD, for example if we reference it from a page. But what good is a store like that if we cannot search it for content? The problem we were trying to solve using ObjectStore was storing comments for ANY Episerver page without having to do anything to the page type. We might need that for the upcoming project so the discovery may prove useful since this is a really neat way of storing objects.

So the class that we are going to store needs to persists the following values:

namespace Cognifide.EPiServerTest.ObjectStore
{
    [Serializable, XmlInclude(typeof (PageCommentDO))]
    public class PageCommentDO : IItem, IPageComment
    {
        private object id;
        private string name;
        [Indexed(true)]
        private int pageId;
        private string title;
        private string content;
        [Indexed(true)]
        private DateTime submitDate;
        [Indexed(true)]
        private bool moderated;
        [Indexed(true)]
        private bool published = true;
        [Indexed(true)]
        private bool reported;
 
    }
}

 You might have noticed that contrary to what I did before this class has some of its fields tagged with [Indexed(true)] attribute. This is required by the store object if we ever want to query the store for the class using those fields as a filtering criteria. The page also implements the required IItem interface (although the properties required by the interface are not shown in the excerpt) as well as our internal IPageComment which is added there for the purpose of easy switching of implementations. We intend to implement it on both ObjectStore and using NHibernate to make sure we get the best performance possible out of our solution. This interface is to allow us to easily switch between the 2 implementations.

Back to the ObjectStore

 First we need to introduce a few new classes EPiServer uses for querying the Store. The namespace that scopes the querying consists of the following entities: 

namespace EPiServer.BaseLibrary.Search
{    
    public class BetweenExpression : IExpression;
    public class EqualExpression : IExpression;
    public sealed class Expression;
    public interface IExpression;
    public class Order;
    public class Query : IEnumerable;
}

The Query class is at the center of any search in the Store, let’s start with the samples right away. The saving is almost identical to the sample that I’ve shown in my previous article:

public void Save()
{
    ISession session = null;
    try
    {
        // check if there is a schema for the type, if not create it.
        if (Context.Repository.SchemaForType(GetType()) == null)
        {
            TypeSchemaBuilder.RegisterSchemaAndType("CognifidePageCommentsStorage", GetType());
        }
 
        // get a new session from the current context.
        session = Context.Repository.CreateSession();
 
        // wrap it in a transaction 
        session.BeginTransaction();
 
        // make sure the id has been defined.
        if (Id.Equals(Guid.Empty))
        {
            Id = Guid.NewGuid();
        }
 
        //persist
        session.Save(this);
        session.CommitTransaction();
    }
    catch (ElektroPostException exception)
    {
        // ... rollback the transaction and do some reporting
        if (session != null)
        {
            session.RollbackTransaction();
        }
        throw;
    }
    finally
    {
        if (session != null)
        {
            session.Close();
        }
    }
}

Really there is not much meat there. The interesting part comes in the object retrieval. Even though the Search namespace does not look like it offers much, you can still build a fairly fophisticated filter with it. following code retrieves comments for a specific PageLinkID. The comments can be filtered by date and since the call is used to retrieve the page for viewing, we probably only want the ones that are public (meaning they have not been removed by the comment moderator).

 

public static List<PageCommentDO> GetCommentsForPage(int pageId, 
    DateTime beforeDate, DateTime afterDate, bool includeOnlyPublished)
{
    ISession session = null;
 
    try
    {
        // make sure there is a schema to read from, otherwise there is no point
        if (Context.Repository.SchemaForType( typeof (PageCommentDO)) == null)
        {
            return null;
        }
 
        //acquire a session
        session = Context.Repository.CreateSession();
 
        //create the query 
        Query query = new Query(typeof (PageCommentDO));
 
        // we only want comments for a singler page 
        query.Add(Expression.Equal("pageId", pageId));
 
        // should we filter to only wshow comments from some set time period?
        // the following demonstrate how to request a value from within a range
        if ((beforeDate != DateTime.MinValue) ||
            ((afterDate != DateTime.MaxValue) && (afterDate != DateTime.MinValue)))
        {
            query.Add(
                Expression.Between("submitDate",
                                   (beforeDate != DateTime.MinValue) ? beforeDate : new DateTime(0x76c, 1, 1),
                                   ((afterDate != DateTime.MinValue) && (afterDate != DateTime.MaxValue))
                                       ? afterDate
                                       : new DateTime(0x834, 1, 1)));
        }
 
        // probably for a displayed page we will only want to show published comments
        if (includeOnlyPublished)
        {
            query.Add(Expression.Equal("published", true));
        }
 
        // order the comments by date/time so that we get the most recent first
        query.AddOrder(new Order("submitDate", true));
 
        IList list = session.ExecuteQueryObject(query);
        List<PageCommentDO> result = new List<PageCommentDO>(list.Count);
        foreach (object item in list)
        {
            result.Add((PageCommentDO) item);
        }
        return result;
    }
    catch (ElektroPostException ex)
    {
        // ... do some reporting
        throw;
    }
    finally
    {
        if (session != null)
        {
            session.Close();
        }
    }
}

You nahe to agree, this really IS neat. All you need to do now to place the comments on a page is a few lines of code to bind the data from the Store with a repeated of a kind, and a set of edit boxes with a submit button :)

This is basically how much code the submission and retrieval the comments comment take:

// Load the comments to a repeater
protected void Page_Load(object sender, EventArgs e)
{
        List<PageCommentDO> comments = 
            PageCommentDO.GetCommentsForPage(CurrentPage.PageLink.ID, DateTime.MinValue, DateTime.MaxValue);
        CurrentComments.DataSource = comments;
        CurrentComments.DataBind();
}
 
// Comment submission
protected void SubmitComment(object sender, System.EventArgs e)
{
    PageCommentDO newComment = new
        PageCommentDO(Guid.Empty,
        string.Format("CommentForPage{0}", CurrentPage.PageLink.ID),
        CurrentPage.PageLink.ID,
        SubjectTextBox.Text, ContentTextBox.Text, DateTime.Now, false, true, false);
    newComment.Save();
}
 
// the data binding for the labels
protected void CurrentComments_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
    PageCommentDO comment = (e.Item.DataItem as PageCommentDO);
 
    Label commentSubjectLabel = (Label)e.Item.FindControl("CommentSubjectLabel");
    if (comment != null)
    {
        commentSubjectLabel.Text = comment.Title;
    }
 
    Label commentContentLabel = (Label)e.Item.FindControl("CommentContentLabel");
    if (comment != null)
    {
        commentContentLabel.Text = comment.Content;
    }
}

Short of having a control pre=built for you I cannot think of it being able to make it easier.

I will look into packing it into a nice control that we would be looking into contributing some time in the future and of course if there is actually a demand for it.

Since IIS on XP seems to be handling only one site at a time and I have multiple installations of EpiServer for different purposes on my machine I needed a fast way of switching between them.

Sure I could go to the administration console and do it manually, but hey, why waste 30 seconds every time you do it when you can actually batch waste them in a chunk of half an hour to automate it.

so here’s my findings.

The adsutil.vbs is the script you want to get intalled on your system somewhere in the search path. %systemroot% will do.
You will find the script on your XP CD in the \i386 folder. Unpack with the following command:

expand E:\i386\adsutil.vb_ %SystemRoot%\adsutil.vbs

Assuming that E: is your CR-ROM drive.

You can find the user’s reference for the script on the Microsoft page

You may want to make sure that your Episerver is running in the default location by using the following call:

Cscript.exe %SystemRoot%\adsutil.vbs SET /W3SVC/1/ROOT

But since you’re probably having only one web server running on your machine anyway (why would you read it otherwise) probably the only thing you really need to do is:

Cscript.exe %SystemRoot%\adsutil.vbs SET /W3SVC/1/ROOT “C:\Inetpub\MyEPiServer###”

Another great tool I use that’s actually built into windows is reg.exe. this tool actually allows you to modify registry from command line. this was actually my initial approach to changing the path. It didn’t succeed to actually change the IIS root – but still it did the modification to the registry (that IIS just merrily ignored).

reg.exe ADD “HKLM\SYSTEM\CurrentControlSet\Services\W3SVC\Parameters\Virtual Roots” /v “/” /d “C:\Inetpub\MyEPiServer###,,201” /t REG_SZ /f

This is however good for changing some database settings if anything is stored in the registry before restarting the IIS, which is can be scripted as:

net stop W3svc
net start W3svc

So to summarize… my script for switching sites ends as:

@echo off

set root_folder=C:\Inetpub\MyEPiServer2\
set adscript_location=%SystemRoot%\adsutil.vbs

echo Switching IIS to %root_folder%
Cscript.exe %adscript_location% SET /W3SVC/1/ROOT “%root_folder%”
net stop W3svc
net start W3svc

Posted in ASP.NET, EPiServer, Internet Information Services
1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...
| 9 Comments »

Google Map Control and why EPiServer is so cool!

I’ve recently had a chance to write a Google Maps control for EPiServer, it’s still somewhat buggy and I’m still considering how to release it since it still contains some java script that is potentially GPL infected and I would not like to contaminate someone’s code with it. I may end up rewriting it to some extent or make it more server side so that it’s completely ASP based.

Anyway…

We’ve started working on the rewrite of our site internally in a few CMS’es basically creating an internal competition on which of the engines/teams can do the best the easiest and the fastest site. I can say honestly, EPiServer has been a blast! Virtually any control we’ve decided to place there was almost completely effortless. The controls that are delivered (with sample usage on the demo site) just seem to cover everything. Well, almost everything. There is no map creation component as far as I can tell.

I’ve been wanting to write this control for quite a while and since I deployed a wiki for my family and started filling it in. I had a really nice experience with this Google Map extension to the MediaWiki. I wanted us to have the same on our site. And in the mean time we’ve started running into some limitations that required us to write some plugins for the editor’s site of the CMS. Striking two birds with one stone, here comes the Google Maps for EpiServer.

Anyone familiar with EpiServer knows that the CMS allows you to define the content on any given page through a set of properties defined for its page type. There is a handful of those, and each of them comes with a specific editor. Some of them even come with so called DOPE (Dynamic-on-page-editing). This feature is really so cool that by itself it’s probably one of the driving selling factor. I wanted it all!

To deliver it you need to inherit a property, (in my case I decided to go with a LongString as I can easily go over the 255 char limit if the user woudl decide to have more than a couple of flagpoints on his/her map) and define its editors.

I’ve found out that the property can be easily integrated with the CMS (virtually without any user intervention) by means of attributes/reflection. So here we go:

namespace Cognifide.EPiServerControls.GoogleMaps

{

  [global::EPiServer.PlugIn.PageDefinitionTypePlugIn(

    DisplayName = “GoogleMapData”, Description = “Google Map”)]

  public class GoogleMapProperty : EPiServer.Core.PropertyLongString

  {

   …

  }

}

Yay! one line of code and my class is a property and will show up in the system as one of the available data formats. Now how cool is that!?

The cool part of it is that now as it’s a property, it’s even easier to integrate it with the page.

<%@ Page language=”c#” Codebehind=”GoogleMapsPage.aspx.cs”

    AutoEventWireup=”True”

    Inherits=”development.Templates.GoogleMapsPage”

    MasterPageFile=”~/templates/MasterPages/MasterPage.master” %>

 

<%@ Register TagPrefix=”EPiServer”

    Namespace=”EPiServer.WebControls” Assembly=”EPiServer” %>

 

<asp:ContentContentPlaceHolderID=”MainRegion” runat=”server”>

  <EPiServer:Property ID=”MyGoogleMap” runat=”server”

  PropertyName=”GoogleMapData” />

</asp:Content>

That’s it! The CodeFile is practically empty, except for the autogenerated part. The property instance knows by itself that is should pull the data from the page property defined in “PropertyName”!

The control supports all 3 modes:

View Mode – obviously:

Google Map View

Edit mode – can’t do without it:

Google Map View

I initially planned to put the dope-like editing there but for some reason EPiServer scripts kept interfering with the JavaScript defined for the control. Didn’t give it too much thought though what I really wanted to work good is…

DOPE mode – this is probably the coolest thing in the whole deal:

Google Map View

The only problem I still have with the last mode is that most of the code for the DOPE mode I have is a modified version of what comes originally from the MediaWiki Google Map extension. Since JavaScript is not my core competence, I’ve only modified it to the extent that was needed for the code to work and therefore before save, you need to copy the dynamically generated code that’s just below the editing controls and into the edit box. Lame, I know. But I don’t really fancy learning Java Script further right now and it was not the point of this exercise. Perhaps if the control is released someone will be kind enough to fix and extend it so that it’s more streamlined.

Cognifide
The article is based on the knowledge I’ve gathered and work I’ve performed for Cognifide. Cognifide is an official partner EPiServer and the real contributor of the the control.

Posted in .Net Framework, ASP.NET, C#, EPiServer, Web applications
1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...
| 24 Comments »

Microsoft loves programmers

I’ve just read a blog about a few new additions to C# 3.0 and in the context of what we’ve already learned about the whole “Orcas” project that is the simplest conclusion.

Microsoft .Net Framework designers and coders are just a bunch of programmers who you can clearly see enjoy hat they do. I can’t stress enough how many times I’ve been annoyed to be forced to wrap some private variables in the obvious public properties. No longer!

Instantiating a class followed by a bunch of setting of properties? Now done in one line. Shweet.

One may argue that C# is a set of such syntactic sugar. But then again, I am sure that’s why so many programmers really like it. Even some of the most Java oriented programmers (Yes Albert I’m looking at you!) in our company are looking forward to work on .Net.

It is the general perception here that, comparing to Eclipse, Visual Studio is a weak IDE in terms of pure code-writing-helpers, refactoring, and discovery of code dependencies. Only the next version will even be able to target more than 1 .net framework version… Please fix that crap… But the language designers are continually doing a great job.

Have a read on some:

Microsoft ,may be the most annoying company in any other context, though you can’t help but to feel that sweet and sticky loving goo leftover on your cursor avery time you flip a page….

My first meaningful EPiServer control… z Biedronki* :)

The challenge – The site that we will be coding will have its pages tagged with episerver categories. Implement a control that will list all the pages tagged with a specific category.
The control aspx code seems looks pretty straightforward and is derivative of some other controls that are defined in the EPiServer sample site:

<%@ Control Language=”C#” AutoEventWireup=”false” CodeFile=”CategoryListing.ascx.cs” Inherits=”development.templates.Units.CategoryListing” TargetSchema=”http://schemas.microsoft.com/intellisense/ie5″ %>

<%@ Register TagPrefix=”EPiServer” Namespace=”EPiServer.WebControls” Assembly=”EPiServer” %>

 

<div id=”rightmenudivStartPage”>                       

    <div class=”listheadingcontainer”>

        <div class=”listheadingleftcorner”></div>

        <a class=”listheading leftfloating” href=”<%=EventRootPage.LinkURL%>“><%= EventRootPage.PageName %></a>

        <div class=”listheadingrightcorner”></div>

    </div>

 

    <EPiServer:PageList runat=”server” ID=”PageList1″>

        <ItemTemplate>

            <div class=”startpagecalendaritem”>

                <span class=”datelistingtext”>

                <episerver:property ID=”Property1″ runat=”server” PropertyName=”PageLink” CssClass=”StartCalendar” />

                <span class=”Normal”><episerver:property ID=”Property3″ runat=”server” PropertyName=”MainIntro” /></span>

            </div>

        </ItemTemplate>

    </EPiServer:PageList>

    <br/><br/>

</div>

The wirst thing you will notice after looking at the code is that PageList is pretty much a standard ASP.NET reinvented and rehashed. GREAT! Sounds like we can use the Data binding, right? That’s also true:

private void Page_Load(object sender, System.EventArgs e)

{

    if (!IsPostBack)

    {

        PageList1.DataSource = CategoryPages;

        DataBind();

    }

}

Does the trick of binding the data to the repeater… uh… oh… sorry… I mean PageList.

Now comes the hard and non-obvious part.

How does one actually find the pages assigned to a category? Short of calling the database directly to query the tblCategorypage table that comes to mind at first, it looks like EPiServer has a really cool page finding module that allows for simple yet fairly effective discovery of pages conforming to some developer defined criteria. Those criteria are defined by means of forming any necessary number of instances of PropertyCriteria class provided as a PropertyCriteriaCollection to Global.EPDataFactory. The result is handed to us back as a PageDataCollection. The fetching of the pages looks like this:

public PageDataCollection GetCategoryPages()

{

    PropertyCriteria criteria = null;

 

    CategoryList categories = ((PropertyCategory)(CurrentPage.Property[“AggregatedCategory”])).Category;

 

    criteria = new PropertyCriteria();

    criteria.Condition = CompareCondition.Equal;

    criteria.Type = PropertyDataType.Category;

    criteria.Value = categories.ToString();

    criteria.Name = “PageCategory”;

    criteria.Required = true;

 

    PropertyCriteriaCollection col = new PropertyCriteriaCollection();

    col.Add(criteria);

 

    PageDataCollection pdc = new PageDataCollection();

    pdc = Global.EPDataFactory.FindPagesWithCriteria(EPiServer.Global.EPConfig.StartPage, col);

 

    return pdc;

}

For this to work though, one has to define some more stuff in the web admin part of the site. the usual task is to define the Page Template for the control. In this case when you define the Page Template make sure to put the “AggregatedCategory” property in that should be of type Category selection. This property defines what categories you want your control to aggregate, and you can provide one or more of those. the other property that is required for this very control is “CategoryContainer” of type Page. This is solely for the purpose of having a master page that you define as the “main page” for the chosen category selection. It serves as a clickable header in the control. Unnecessary maybe but one would have to define a caption for the control anyway so why not make it meaningful.

The usage of the said control is trivial. A sample usage on the default EPiServer site looks like this:

<%@ Page language=”c#” Codebehind=”CategorySummary.aspx.cs” AutoEventWireup=”false” Inherits=”development.Templates.CategorySummary” %>

<%@ Register TagPrefix=”EPiServer” Namespace=”EPiServer.WebControls” Assembly=”EPiServer” %>

<%@ Register TagPrefix=”development” TagName=”CategoryListing”  Src=”~/templates/Units/CategoryListing.ascx”%>

<%@ Register TagPrefix=”development” TagName=”DefaultFramework”    Src=”~/templates/Frameworks/DefaultFramework.ascx”%>

 

<development:DefaultFramework ID=”DefaultFramework” runat=”server”>

    <EPiServer:Content ID=”Content1″ Region=”fullRegion” runat=”server”>

        <development:CategoryListing  ID=”CategoryContent” runat=”server”/>

    </EPiServer:Content>

</development:DefaultFramework>

Out of which really only the <development:CategoryListing  ID=”CategoryContent” runat=”server”/> part is meaningful.

For the record… It looks like THE place to go to solve that kind of dilemas fast is the EpiServer “Developer to Developer” forums.

*)… couldn’t ressist with the title joke :) For those not in subject… It’s a reference to a silly commercial of one of the market chain (Biedronka) in Poland that advertises recently with “My first *something*… from Biedronka*

Cognifide
The article is based on the knowledge I’ve gathered and work I’ve performed for Cognifide. Cognifide is an official partner EPiServer and the real contributor of the the control.

Posted in ASP.NET, C#, EPiServer, Internet Information Services
1 Star2 Stars3 Stars4 Stars5 Stars (1 votes, average: 5.00 out of 5)
Loading...
| 21 Comments »

EPiServer fun

EPiServer is an Interesting technology we’ve started working on recently. I will try to blog my impressions and the progress over the course of learning the solution.

Since I just seem unable to learn by reading docs I chose to build an email obfuscating (antispam) control and a paged search as an exercise and a way to learn the guts of the EPiServer.

A couple of loose thoughts for a start…

Translation

I am not sure I fully appreciate the way the translation is performed for the parts of the system that is editor independent . The translation is done by means of xml files stored on the disk in the /lang folder.

Basically what that means is that it’s much more prone to missing translations and thus is not as translation friendly as it could fairly easily be.

For the content I can always fall back to the e.g. english version and look what’s the original value there. not so much for the framework translations. Is there a tool for that? I will investigate that later as we’ll probably want to create a number of controls for the website we’ll be working on soon, and that will need to be translated to many languages. And not just that but also the original template files – we will need much more than what’s available originally in EPiServer.

So once you define your control’s content:

<asp:LinkButton ID=”Obfuscate” runat=”server” CausesValidation=”False” OnClick=”ObfuscateEmail”>

    <episerver:translate Text=”/templates/emailobfuscator/obfuscate runat=”server” ID=”Translate3″ />

</asp:LinkButton>

in which you defined where the translation is to come from. (In this case the path is: “/templates/emailobfuscator/obfuscate”) you need to edito the template framework file and add the translation there with the XPath defined in the control. Which looks along the lines of:

<?xml version=1.0 encoding=utf-8 standalone=yes?>

<languages>

    <language name=English id=EN>

        <templates>

            <emailobfuscator>

                <obfuscate>Obfuscate</obfuscate>

            </emailobfuscator>

            …

        </templates>

        …

    </language>

</languages>

The most annoying part of it though is that it needs to be done for alll the languages if you want the site to be fully translated, which without a tool is not fun.

I will look more into that later. One would expect that a tool like that may exist already.

Technology

I am really looking forward to EPiServer 5.x to be released. It’s to be based on ASP.NET 2.0 which most probably means (I hope)that a number of EPiServer specific technologies will be replaced by a .Net generic technologies. As the ElektroPost notices:

EPiServer Content Framework Is Not Unlike ASP.NET 2.0 Master Pages and Content Pages.

Also the EPiServer seems to be really hard to develop for in VS.Net 2005. I still didn’t Indeed ElektroPost suggests VS 2003 as the development platform, but once switched to 2005 I personally can’t deal with VS 2003 any longer.

The good news though is that it is compatible with .Net 2.0 in a way that you can simply add an ASP 2.0 control and with just slight modifications work with it. You can also use .Net 2.0 partial classes – which means much cleaner code.

Overall the EPiServer is a really positive experience. I’m looking forward to work more with it.

Cognifide
The article is based on the knowledge I’ve gathered and work I’ve performed for Cognifide. Cognifide is an official partner EPiServer and the real contributor of the the control.

Linkage

I’ve done some research about Visual studio plugins recently, just so that I can close the tabs and move on here are some links that I cound to contain some useful information:

Learning C#

I?m about to help a few guys here with their transition from Java to .Net, namely to C#. 

I thought it may actually be a good idea to gather a few helpful links together for you. Stuff I found useful while I was phasing into the joys of the .Net framework a while ago.

The official stuff

First and foremost C# is an ECMA regulated standard. And as such is pretty well documented. ECMA allows you to download PDF e-books regarding the standardized part of the language:

The books are a free download, you could theoretically request the books in printed form (for free) which I did back in my time when I was learning C# but it looks like they have a strict publications ordering form with a combo box of publications which? surprisingly do not list entries 334, 335 or 372. I suppose there was a big number of those smart guys who smelled free books? and the trees were crying.

There is also a good explanation (although highly redundant) on MSDN pages.

Hint: I highly suggest that you read the Garbage collection part of that. It REALLY helps in your day-to-day problems.

I have to say I?m really impressed by the official documentation. I was initially trying to shop around for some C# books back in my days of learning but since there seemed to be none that were both accessible here without waiting for a month or so for delivery and worthwhile I reverted to official docs. And I can tell you, THEY ROCK.

For all your ASP.NET need visit? surprise, surprise? ASP.NET official page.

You may also want to read up on the IIS official page for getting accustomed with IIS. For what it?s worth, there are some articles here.

I would say? don?t buy any books for it, it?s all there and it?s written really well.

All in all, I?m really impressed at how well this stuff is documented, Microsoft really did a great job here. Makes me feel taken care of. But after all? when in doubt – throw money at it.

The good stuff

It looks like for almost every problem you may have there is a simple, clean and not-quite-accurate solution out there.

For those I usually look at CodeProject. Amazing how many of those unique problems you may have, are not so unique. Just put up a proper filter (C#, .Net, ASP.NET) and fill in a few words. Amazing stuff there.

.Net framework does not fully cover all the old stuff of the WinAPI. I do not suspect we will need much of it here, but if you ever needed to use some legacy windows DLL access for stuff not directly supported by the platform (like e.g. speedy reading INI files, or manipulation of Windows handles to get a per-pixel translucent windows ) there is a cool site that helps, which is called exactly like the gears that are used to do it – Pinvoke. There are other marginally useful sites like CodeGuru, DevX or C#Corner but I don?t use them nearly as much as CodeProject or Google.

Hint: when on Google try to use Google Groups rather than just the Google Web search engine.

Posted in .Net Framework, ASP.NET, C#, Software Development
1 Star2 Stars3 Stars4 Stars5 Stars (No Ratings Yet)
Loading...
| 18 Comments »