in

ASP.NET Weblogs

uber1024's WebLog

It's not hot wings and beer, but it's still okay

April 2004 - Posts

  • Relocating to another country?

    Has anyone ever gotten a tech job in a country that's not your own?  How did you go about doing it?  What problems did you run into, assuming that language was not one of them?

    I've asked around and no one has any ideas, so this is the last place that I can think of to ask.  I figured I would just do what I always do and just move there and try to make something happen, but until now I've always been moving from one place in the US to another.  Now I'm moving to Seoul, South Korea for the summer so this is a little different.

    Posted Apr 29 2004, 04:28 PM by uber1024 with 8 comment(s)
    Filed under:
  • Making an editable datagrid

    I've been getting away from DataGrids more and more lately, but there's still no substitute for an editable DataGrid for administrative pages:

    <asp:DataGrid Runat=server ID=grdSeries DataKeyField="series_id" AutoGenerateColumns=False CellPadding=2 CellSpacing=2 BorderWidth=0>
      <Columns>   
       <asp:EditCommandColumn EditText="Edit Series" CancelText="Cancel" UpdateText="OK"></asp:EditCommandColumn>
       <asp:TemplateColumn HeaderText="Series Name" >
        <ItemTemplate>
         <asp:Label Runat=server Text='<%# DataBinder.Eval(Container.DataItem, "series_name") %>' ID="Label1"></asp:Label>
        </ItemTemplate>
        <EditItemTemplate>
         <asp:TextBox ID="txtEditName" Runat=server Text='<%# DataBinder.Eval(Container.DataItem, "series_name") %>'></asp:TextBox>
        </EditItemTemplate>
       </asp:TemplateColumn>
       <asp:TemplateColumn HeaderText="Description" >
        <ItemTemplate>
         <asp:Label Runat=server Text='<%# DataBinder.Eval(Container.DataItem, "description") %>' ID="Label2"></asp:Label>
        </ItemTemplate>
        <EditItemTemplate>
         <asp:TextBox ID=txtEditDescription Runat=server Text='<%# DataBinder.Eval(Container.DataItem, "description") %>'></asp:TextBox>
        </EditItemTemplate>
       </asp:TemplateColumn>
      </Columns>
     </asp:DataGrid>

    Now for the events in the code behind that run this:

    private void grdSeries_Cancel(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e)
      {
       grdSeries.EditItemIndex = -1;
       SubmitSearch();
      }

      private void grdSeries_Edit(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e)
      {
       grdSeries.EditItemIndex = Convert.ToInt32(e.Item.ItemIndex);
       SubmitSearch();
      }

      private void grdSeries_Update(object source, System.Web.UI.WebControls.DataGridCommandEventArgs e)
      {
       Series objSeries = new Series();

       objSeries.SeriesName = ((TextBox)e.Item.FindControl("txtEditName")).Text;
       objSeries.SeriesDescription = ((TextBox)e.Item.FindControl("txtEditDescription")).Text;
       objSeries.SeriesID = Convert.ToInt32(grdSeries.DataKeys[e.Item.ItemIndex]);

       if (!objSeries.Save())
       {
        lblMessage.Text = "There was an error updating your series changes.";
       }

       grdSeries.EditItemIndex = -1;
       SubmitSearch();
      }

    where SubmitSearch() just does the search and repopulates the grid.  The trick to getting this to work is pulling the data from the controls from the editable row out of the DataGridCommandEventArgs parameter using “FindControl” and casting it to the correct type.  Also note the use of the DataKeyField to relate each row with a primary key in a database.

    Hopefully this sparks some ideas with someone out there.  I know that when I was “coming through the ranks” of ASP.NET, when I realized I could make a DataGrid that was editable in place that was the moment that I realized that you could do some REALLY cool things with ASP.NET.

  • "Six Degrees of Separation Search" Stored Procedure

    One of my websites (forever unfinished, although we set May 1st as the day that we're “done“) is a comic book website that a graphic designer friend of mine and I work on in our spare time.  This was the first site that I did end-to-end in ASP.NET and C# and, of course, there's a lot of things I would do differently now, a year and a half later.  Basicly, the site is a database of characters, comic books and appearances (and it's a great excuse to give to our wives/girlfriends about why a couple of guys who are ostensibly adults need to go to a comic book convention).  Since this was something we were doing to improve our repective skills and try new things, I made a very simple web service that allows you do to a “Back to Bacon” type of search of our database where you give it two characters from comic books and the database tries to connect them (ie, you say “A” and “B”, and the database says “A” appeared with “C” who appeared with “D” who appeared with “B” and there's your connection).  The meat of the web service is the stored procedure that drives the whole thing, and it accepts two strings, each one being a character name and starts the search, and that's what I want to look at today.  What you'll need to know is that there is a table called “appearances“ that contains information about characters, “issues“ that contains information about specific issues of comics and “appearance_xref“ which resolves the two and logs which characters appeared in which comics.  I inherited the database schema from another programmer, so I didn't name those tables.

    CREATE PROCEDURE spMarvel_CharacterSeperation @character_one varchar(100), @character_two varchar(100)
    AS
    set nocount on
    declare @MAX_DEPTH int
    select @MAX_DEPTH = 6

    declare @appearance1 int
    declare @appearance2 int

    select @appearance1 = appearance_id from appearances where name = @character_one
    select @appearance2 = appearance_id from appearances where name = @character_two

    declare @intDegrees int
    declare @blnDone bit
    declare @strIssueName varchar(300)
    declare @intFirewallCount int

    declare @strName1 varchar(100)
    declare @strName2 varchar(100)

    declare @intIDToKeep int

    if (@appearance1 is null)
     begin
      RAISERROR('Character 1 not found',16,1)
      return (-1)
     end
    if (@appearance2 is null)
     begin
      RAISERROR('Character 2 not found',16,1)
      return (-1)
     end

    create table #tbTempDegrees
    (
     appearance_one int,
     appearance_two int,
     name_one varchar(100),
     name_two varchar(100),
     issue_id int,
     issue_name varchar(300),
     dos int,
     intState int  -- 0 = just added, 1 = searching, 2 = searched
    )

    -- begin search
    insert into #tbTempDegrees (appearance_two, appearance_one, issue_id, issue_name, dos, intState)
     select distinct(x2.appearance_id), x1.appearance_id, Min(x2.issue_id), null, 1, 0
     from appearance_xref x2 join
      appearance_xref x1 on (x2.issue_id = x1.issue_id)
     where x1.appearance_id = @appearance1 and x2.appearance_id <> @appearance1
     group by x2.appearance_id, x1.appearance_id

    select @intDegrees = 1
    select @blnDone = 0
    select @intFirewallCount = 0

    while @blnDone = 0
     begin
      -- firewall code
      select @intFirewallCount = @intFirewallCount + 1
      if (@intFirewallCount > 1000)
       begin
        drop table #tbTempDegrees
        RAISERROR('Infinite Loop encountered', 16, 1)
        return (-1)
       end

      -- check to see if we're done with the search
      if (select count(*) from #tbTempDegrees where appearance_two = @appearance2) > 0
       select @blnDone = 1

      select @intDegrees = @intDegrees + 1
      if @intDegrees > @MAX_DEPTH
       begin
        drop table #tbTempDegrees
        RAISERROR('Searched too far', 16, 1)
        return (-1)
       end

      -- if we're not done with the search, add the next level

      if @blnDone = 0
       begin
        update #tbTempDegrees set intState = 2 where intState = 1
        update #tbTempDegrees set intState = 1 where intState = 0

        insert into #tbTempDegrees (appearance_two, appearance_one, issue_id, issue_name, dos, intState)
         select distinct(x2.appearance_id), x1.appearance_id, Min(x2.issue_id), null, @intDegrees, 0
         from appearance_xref x2 join
          appearance_xref x1 on (x2.issue_id = x1.issue_id)
         where x1.appearance_id in (select appearance_two from #tbTempDegrees where intState = 1)
          and x2.appearance_id not in (select appearance_two from #tbTempDegrees where intState = 1)
          and x2.appearance_id not in (select appearance_one from #tbTempDegrees where intState > 0)
         group by x2.appearance_id, x1.appearance_id
       end
     end

    -- now we should be done, and the connection should have been made ...
    -- so we delete the extraneous data
    select @intDegrees = MAX(dos) from #tbTempDegrees
     
    delete from #tbTempDegrees
    where dos = @intDegrees and appearance_two <> @appearance2

    -- check for multiple paths

    if (select count(*) from #tbTempDegrees where appearance_two = @appearance2 and dos = @intDegrees) > 1
     begin
      select @intIDToKeep = MIN(appearance_one) from #tbTempDegrees where appearance_two = @appearance2 and dos = @intDegrees
      delete from #tbTempDegrees where appearance_one <> @intIDToKeep and appearance_two = @appearance2 and dos = @intDegrees
     end

    select @strIssueName = (series + ' #' + cast(issue as varchar(10))) from issue where issue_id = (select MAX(issue_id) from #tbTempDegrees where dos = @intDegrees)
    select @strName1 = name from appearances where appearance_id = (select MAX(appearance_one) from #tbTempDegrees where dos = @intDegrees)
    select @strName2 = name from appearances where appearance_id = (select MAX(appearance_two) from #tbTempDegrees where dos = @intDegrees)
    update #tbTempDegrees
     set issue_name = @strIssueName, name_one = @strName1, name_two = @strName2
     where dos = @intDegrees

    while @intDegrees > 1
     begin
      -- firewall code
      select @intFirewallCount = @intFirewallCount + 1
      if (@intFirewallCount > 1000)
       begin
        drop table #tbTempDegrees
        RAISERROR('Infinite Loop encountered', 16, 1)
        return (-1)
       end

      select @intDegrees = @intDegrees - 1

      
      delete from #tbTempDegrees
       where dos = @intDegrees and appearance_two not in (select appearance_one from #tbTempDegrees where dos = (@intDegrees + 1))

      -- check for multiple paths
      if (select count(*) from #tbTempDegrees where appearance_two in (select appearance_one from #tbTempDegrees where dos = (@intDegrees + 1)) and dos = @intDegrees) > 1
       begin
        select @intIDToKeep = MAX(appearance_one) from #tbTempDegrees where appearance_two in (select appearance_one from #tbTempDegrees where dos = (@intDegrees + 1)) and dos = @intDegrees
        delete from #tbTempDegrees where appearance_one <> @intIDToKeep and appearance_two in (select appearance_one from #tbTempDegrees where dos = (@intDegrees + 1)) and dos = @intDegrees
       end

      select @strIssueName = (series + ' #' + cast(issue as varchar(10))) from issue where issue_id = (select MAX(issue_id) from #tbTempDegrees where dos = @intDegrees)
      select @strName1 = name from appearances where appearance_id = (select MAX(appearance_one) from #tbTempDegrees where dos = @intDegrees)
      select @strName2 = name from appearances where appearance_id = (select MAX(appearance_two) from #tbTempDegrees where dos = @intDegrees)
      update #tbTempDegrees
       set issue_name = @strIssueName, name_one = @strName1, name_two = @strName2
       where dos = @intDegrees
     end


    -- clean up
    select distinct name_one, appearance_one, name_two, appearance_two, issue_id, issue_name, dos
    from #tbTempDegrees
    order by dos

    drop table #tbTempDegrees
    set nocount off

    Let's go through all that code, shall we?  The first thing we do is to declare all our local variables.  I know that in Code Complete, they say that you should declare them all when you need them, and in my C# I do.  I really, really do.  But for some reason, the coding practices I use for T-SQL are a LOT different from what I do in C#.  I have a variable in there called @MAX_DEGREES which I just use to make sure the database doesn't blow itself up trying to connect two characters that haven't yet been connected in our database.  You'll also notice a variable called @intFirewallCount which I use in any stored procedure that I write that uses loops or cursors.  If you see that variable, you know I wrote the proc.  Every time I go through a loop, I increment that counter and if it ever gets to some high number the proc aborts.  After you've written a few infinite loops on your SQL Server, you start to err on the side of caution (at my last job in PA, our dev SQL server had remote query timeout set to infinite ... which can be good or bad but in my case was always bad).  We validate our parameters and then declare a temp table that we'll use for everything in here and insert our first rows of data.

    Now, what we do is to look at the “current data set” and find all the characters in it.  The first pass through the loop we've got one character to relate to.  Then we find everyone that appeared in a comic with any of those characters and add them to our temp table with a status of “pending”.  If we find our “target character” we're done and we exit that part of the loop.  If not, we mark our “current” data set as “searched” and the “pending” data set as “current” and go through again.  Either we find our target or we search too far and abort.

    Assuming we find our target, we clear out everything in the last data set except for 1 row that contains our target and build the correct information about that particular issue that they appeared in and the characters' names.  We start working our way back through the table (we saved information about which pass through the loop each row was on, and this is why) repeating this process of deleting all the rows except for 1 and looking up the issue data and names.

    Now we've got everything we need so we return it to the web server and clean up the table.  This isn't the best T-SQL that I could have written, but the point is that its an example of looping and taking what is basically a recursive algorithm and unrolling it into an iterative one.  It also shows what a defensive programmer I am in T-SQL whereas I'm usually not so defensive in C#.  This procedure is relatively slow due to the large number if “in” and “not in” clauses that I needed.  I didn't know how else to do that part of the search.  The algorithm is basically a brute force one, as most recursive algorithms are, and no attempt is made to cache searches anywhere. 

    I'm sure that there are things that I could have done better ... and I'd love to hear about them.  I really like T-SQL, and my posts about it seem to generate some good discussion and so hopefully someone will have some thoughts as to what I could have done differently.  I'm totally self taught with SQL Server and so some of the things that I do wind up being “second rate” techniques that I wouldn't mind getting rid of.  On the other hand, I know a lot of developers that don't think to do so much work on the database and wind up generating huge numbers of calls to the SQL Server to accomplish the same task that I'd do in one, and so maybe this will get them thinking about ways that they could do more on the database.

    As an aside about doing websites on my own for fun ... the graphic designers that I work with are good friends of mine and sometimes getting together to discuss things about our sites or do to things related to our sites (comic book conventions, football games, training camp) is a great reason to just get together.  We would probably go to them anyway, but having a site related to that gives us extra incentive and that's really helped since I moved away.  They both live in Pennsylvania and I live in New York City now, so sometimes we only see each other for site-related events.

  • SPS2003 - Machine Administrators have full control over the site?

    I noticed that my boss, who was not even listed as a reader on our SPS2003 development site, had the ability to create subareas, add webparts to them, add listings, and add users.  In fact, I think that he's the one that added me as a user to the site and made me an administrator.  We ran a few tests with people who were administrators to the MACHINE and they could do whatever they wanted to the whole portal site.  Then we took users that were not administrators to the machine and they could only do what we specifically provisioned them to do.

    So, our current theory is that local administrators to the server are allowed to do whatever they want to our Portal.  Anyone notice this?  If so, any thoughts?

  • Quick and Dirty paging with SQL Server

    I stumbled across this overload for the Fill method of the SqlDataAdapter:

    public int Fill(
       DataSet dataSet,
       int startRecord,
       int maxRecords,
       string srcTable
    );

    So you could do something like:

    objDA.Fill(objDS, (intPageSize * (intPageNumber-1)), intPageSize, “TableName”);

    I believe that the method will actually return all of the records from your query, but only add the ones you specify to the DataSet.  If that's the case, then I wouldn't recommend using this in a highly performance sensitive application, but if you just need to get something out the door this might be a quick and dirty way to do it.  Much easier than what I did in my last post

  • Paging in a Stored Proc using nested selects

    Lately, I've been doing whatever I can to rewrite DataGrids as Repeaters, and I must say that the performance increase has been noticable.  I haven't tried to measure and quantitatively document the increase, but suffice to say that when I redid my first DataGrid and reloaded the page once or twice to compile it and cache what the page wanted to cache, I thought “wow, that's fast compared to how it used to be,” and that's what matters to my users.  The first part of this process was to write the stored procedure that would do the paging.  Let me preface this by saying that I'm SURE that there's a better way to do this, and I recently read a blog entry with an idea that I hadn't thought of that I might try later.  I'm totally self-taught with T-SQL, as I've typically worked with other developers who were not DBAs and could write basic INSERT, UPDATE, and DELETE statements but complex T-SQL was not what they did ... so I wound up being the de facto “T-SQL guy” most of the time.  I should also say that this code is used in the messageboards of my site and that the column names, proc name, and table names have all been changed and more comments have been added.  So, here's my stored proc, and we'll break it down afterwards:

    CREATE PROCEDURE spForums_Replies_List @topic_id int, @page_id int, @page_size int
    AS
    declare @strSQL varchar(7900)

    declare @TotalCount int

    select @TotalCount = count(*) from tbForum_Replies where topic_id = @topic_id

    if @page_id = 0   -- first page is a simple case, so keep it that way
     begin
      select @strSQL = 'select top ' + cast(@page_size as varchar(10)) + ' reply_id, message_text, added_date, subject_line, creator_name, forum_id, topic_id from tbForum_Reply where topic_id = ' + cast(@topic_id as varchar(10)) + ' order by added_date'
     end
    else
     begin
      declare @PageCount int
      
      --get @PageCount, which makes sure that we select the right number of records on the last page of the thread
      IF((@page_id + 1) * @page_size > @TotalCount)
       BEGIN
        SELECT @PageCount = @TotalCount % @page_size
       END
      ELSE
       BEGIN
        SELECT @PageCount = @page_size
       END
      

      select @strSQL = 'select top ' + cast(@PageCount as varchar(10)) + ' * from ( select top ' + cast(@PageCount as varchar(10)) + ' * from ('
      select @strSQL = @strSQL + 'select top ' + cast((@page_size * (@page_id+1)) as varchar(10)) + ' reply_id, message_text, added_date, subject_line, creator_name, forum_id, topic_id '
      select @strSQL = @strSQL + 'from tbForum_Reply where topic_id = ' + cast(@topic_id as varchar(10)) + ' order by added_date ) as x order by added_date desc ) as y order by added_date'
     end

    select @TotalCount as thread_size

    exec (@strSQL)

    So that's it.  The first thing that we do is to calculate the number of items there are, total, in the thread.  We need this to calculate how many pages we want to display in the footer of the messageboard (which I'll soon provide code for).  Then, if there is only going to be one page, then we just build a simple SQL statement to handle that case and that's pretty much the end.  If there are more pages then we need to next decide if we're on the last page and if we are then we need to compute how many replies should be returned.  Now we know how many records to get, so next comes the nested SELECTs.  The innermost select gets all the replies from the first in the thread to the last one we're going to select, in order.  The middle select picks out just the replies from the page that we want, but it can only do that by selecting the top ones from the list that we just SELECTed in reverse.  Then the outermost select has the right result set, and it just orders it correctly.  If I wanted this result set in reverse order, I wouldn't need that outermost SELECT.

    As you can see, I build up the SQL in a string and then run it at the end.  I've never been able to get the SELECT TOP statement to work with a variable, so I have always had to build the SQL up and then execute it later.  I don't like having to do this, but sometimes it's the only way.  I typically also have to do this when the ORDER BY clause is determined outside of the stored proc and passed in.  I've run this proc in Query Analyzer many times (and the database is remote from me so it all goes across the internet) and it's never taken more than 1 second, so I'm happy with it.

    Also, notice the naming convention that I use for tables ... I use the “component” which in this case is “Forum” then the “object” which in this case is “Reply”.  Similiarly for the name of the stored procedure, I use the component, then the object, then maybe another noun if I need it and then the verb.  I find this easier to deal with than having the verb first because then all the related stored procedures are grouped together.  It never made much sense to me to have all the SELECT stored procedures together then all the DELETE ones together ... but that's just me.

    Hopefully this has been some food for thought.  I'm sure that I'll get at least one or two “that's so inefficient” or “that's the WRONG WAY to do ...” comments and that's fine.  I will try to take them as constructive criticism and get better.

  • Storing Viewstate in other places by overriding the Page object

    When building the messageboards of the 700level, I was coming across a situation where my ViewState was winding up being on the order of 50K-100K which was unacceptable for dialup users. In addition, they were the most heavily trafficked pages with users sitting on them clicking "refresh" to see the latest posts so I was afraid of what would happen to our bandwidth usage during playoff times (the site is a Philadelphia Eagles fansite). At the time, I needed the viewstate to handle events such as the PageChanged event of the DataGrid. I've since rewritten one of the DataGrids as a Repeater and implemented all my own paging code, so this code will soon come out of my site, but I wanted to document it here before that happened in case anyone else might find this useful ... perhaps on an intranet-type of project where the userbase can be calculated in advance.

    I'll just paste the code and talk about it more at the end:

    public class ViewStateService : System.Web.UI.Page
    {
      public
    ViewStateService()
      {
      }
      protected override object LoadPageStateFromPersistenceMedium()
      {
        LosFormatter oLosFormatter =
    new LosFormatter();
        if (Session["ViewState"] == null)
        {
          Response.Redirect( "/fansview/default.aspx",
    true );
        }
        return oLosFormatter.Deserialize(Session["ViewState"].ToString());
      }
      protected override void SavePageStateToPersistenceMedium(object viewState)
      {
        LosFormatter oLosFormatter =
    new LosFormatter();
        StringWriter oStringWriter =
    new StringWriter();

        oLosFormatter.Serialize(oStringWriter, viewState);
        Session["ViewState"] = oStringWriter.ToString();
      }
      protected override void Render(HtmlTextWriter writer)
      {
        if (Request.Path != null)
        {
          writer =
    new MyWriter(writer);
        }
        base.Render (writer); 
      }
    }

    Okay, looking over this, the important parts are LoadPageStateFromPersistanceMedium which now checks a Session variable instead of the hidden field on the page and SavePageStateFromPersistanceMedium which does the same thing in reverse. Notice how I check to see if the Session variable exists and, if not, I bounce the user back to the default page for that section. I couldn't think of anything better to do with a user with an expired session and I didn't want their page to blow up with the viewstate error, so I sent them back to the list of forums. The LosFormatter objects pretty much just serialize and deserialize all of the state information into the garbage that you see when you look at the viewstate.

    The overriding of the Render method is being done to override the default HtmlTextWriter behavior like I describe in this post about removing the action attribute from the form tag. You can remove that safely and still have your ViewState functionality working. I just included it in this post because I said I would in my last one.

    The implications of this were that I would wind up using about 80K (on average) of Session space for each user. I usually have around 30 users on the site, maybe 50 or 60 during the playoffs, and it handled it well. A quick calculation shows that 50 users would use about 4 megs of RAM ... very doable today and in my mind and adequate tradeoff for not having to force that 80K of data down a 56K pipe, especially since my graphic designer uses a lot of graphics in our sites that our users have to download, so I didn't want to make users download anything more than they needed from me. Most of the site does not use this new Page object and the pages have viewstate as normal, but the pages that form the engine that drives the messageboards do use this page (and they are by far the most heavily used pages ... I think the page that displays threads gets about 40 times the number of hits as the next most heavily used section of the site).

    So I hope this helps someone out there, or at least gets them thinking about some of the possibilities of things you can do with ASP.NET. If you have time, I suggest you dig through the Page class's protected methods and see what kinds of things it's doing under the hood. You'll get a much better understanding of how pages are built by the runtime.  By the way, I'm not saying that this is the BEST way to do this or that storing ViewState in Sessions is the BEST approach to handling this.

  • A curious thing about the CategoryNavigationWebPart

    I had the following line in the default.aspx page of my new template:

    <SPSWC:CategoryNavigationWebPart runat="server" id="VerticalNavBar" DisplayStyle="VerticalOneLayer" />

    and, as I reported earlier, the side navigation wasn't working correctly.  It was displaying the navigation from the home page instead of the current area.  For some reason, changing the id made everything work right:

    <SPSWC:CategoryNavigationWebPart runat="server" id="VerticalNavBar2" DisplayStyle="VerticalOneLayer" />

    I'm not sure why “VerticalNavBar” works for every other template, but I have to rename the control for my template.  My guess is that I need to crack open the XML files and start searching.  Thoughts, anyone?

  • How to render form tag with action=""

    I've seen a couple of posts to ASP.NET forums to the effect of “URLRewriting is dumb because the action attribute of the form tag will point to the wrong place.”  What you need to do is to create your own version of the HtmlTextWriter class that overrides the default behavior.  Here's what I came up with:

    public class MyWriter: HtmlTextWriter
    {
      bool blnIsForm = false;
      private TextWriter writer;
      public MyWriter(TextWriter writer): base(writer)
      {
        this.writer = writer;
      }
      public MyWriter(TextWriter writer, string tabString): base(writer, tabString)
      {
       this.writer = writer;
      }
      public override void RenderBeginTag(string tagName)
      {
        blnIsForm = String.Compare(tagName,"form") == 0;
        base.RenderBeginTag (tagName);
      }
      public override void WriteAttribute(string name, string value, bool fEncode)
      {
        if(String.Compare(name, "action", true) == 0)
        {
          value = "";
        }
        base.WriteAttribute (name, value, fEncode);
      }
    }

    So there you have it Jimmy-Jimmy.  The important parts of this are the RenderBeginTag checks to see if it is currently rendering a form tag and the WriteAttribute checks to see if it is rendering the action attribute and, if so, it just changes the actual value with an empty string.  In a later post, I will show how to call this new writer from the Page object.

    By the way ... does anyone have a better way to insert code into a post than pasting it in and then modifying the HTML?

  • What the crap does TEMPLATE mean in SPS2003?

    So I created a new template and made two pages that use that template.  Customized each page.  That's fine.  That works.  But for some reason the SPSWC:CategoryNavigationWebPart shows the same subareas for each page.  The SPSWC:BreadCrumbTrail part is just fine.

    I'm having trouble figuring out what information is replicated or shared between instances of a template.  I KNOW that there has to be a way to make one or two templates and have the majority of the site use those templates.  I KNOW it.  Why else would they be called “TEMPLATES”?  But until I figure everything out, I guess I will just have to strap the boxes of glass back on my feet and continue making painful babysteps.

More Posts Next page »