Archives

Archives / 2009
  • ASP.NET Markup Guide

    One of the biggest gaps between developers and designers at our company is the disconnect between ASP.NET web controls and HTML markup.

    Our designers are very comfortable working in HTML, and passionate about using semantic markup to communicate the structure and meaning of web sites.  Our developers, on the other hand, understand the functionality of most of the web controls that ASP.NET provides, but are usually more concerned with the functionality that they provide, rather than the markup that the produce.

    To help begin bridging this gap, I created an interactive guide to the markup produced by the various standard ASP.NET web controls.  You can take a look and contribute at the repository on GitHub.

    Right now, when you pull the files down from the repository, you'll have a control with the actual guide on it, another control which you can register with [DotNetNuke] to make it into a [DNN] module, and a plain ASP page, Default.aspx, which you can use to view the guide outside of [DNN] (Phil Haack's Web Server Here shell extension might be useful for viewing that page without having to setup any real website).

    The guide consists of three columns in a table.  The left column contains some ASP.NET markup.  The right column contains the rendering of that markup.  The middle column contains a list of the HTML elements and their attributes which the control renders.  The contents of the middle column are dynamically generated when the page loads, using jQuery.  This means that the results are always accurate for your current situation (whether you're using a downlevel browser, ASP.NET 4.0's new cleaner markup, the CSS Friendly Control Adapters, or just want to see how the control tree on your site affects the rendered Client IDs).  Also, when you hover your mouse over that middle column, the entirety of the HTML is displayed in a tooltip.

    At this point, I think the guide is useful and helpful, but there are definitely improvements to be made.  Firstly, some styling, so that it's not painful to the eyes to find this information.  Secondly, some of the more complicated controls could probably use some more examples, and the controls that aren't in the "Standard" toolbox also need to be represented (e.g. GridView, ListView, etc).  Also, some sort of navigation or searching, especially with the addition of more examples.

    If you find this useful, leave a comment, and consider forking the repository on GitHub and implementing an extra example, or a new feature.

    Hopefully this can help us all get on the same page, and know when an <asp:Label/> is a <label> and when it's a <span>.

  • [Personal] Purple Lemon Photography is on the web!

    Earlier this year, my wife, Nikki, and I started her own photography business, Purple Lemon Photography.  We've had a lot of fun getting setup and figuring out how to run a busines, as well as seeing how people respond to the images that Nikki takes.

    Tonight marks a special night for us, and myself especially, because we finally launched her public facing website!  Go take a look.  This was a lot of fun for me, since it was the first site where I've been fully responsible for most every aspect.  I've had fun figuring out how to mold HTML & CSS in ways I'd never tried before, and am still working through getting everything out of IIS that I can.  I've also used the opportunity to start getting familiar with Git (aided by Jason Meridth's introductory Git for Windows Developers series and a free Unfuddle account).

    We still have a lot of ideas for improvements that we'll be rolling out over the next weeks and months, but we're glad to finally have somewhere nice to point people.  Let us know what you think!

  • Embedding JavaScript and Other Resources In a .NET Assembly

    One concept that I don’t see people using, understanding, or even being aware of much in .NET development circles is the Embedded Resource.  Declaring a file as an embedded resource within your project means that it will be compiled into the assembly, so that you don’t have to distribute the actual file with the assembly.

    In our projects, we embed many of the resources that we use, in order to reduce the number of files that we have to distribute.  The biggest use of this policy is towards JavaScript files.  We use a lot of JavaScript in our modules, but we don’t have to distribute any .js files, they all live in a .dll.  This also makes it a lot easier to automatically minify the JavaScript files when building, but that’s a story for another time.Properties page for swfobject.2.1.js with Build Action property selected

    Down at the end of this post, I’ll also talk about embedding files that are intended to be loaded in memory to be used, rather than accessed through the web.  In that case, it’s much more convenient to already have the file in the assembly, rather than having to search through the file system for it.

    So, how do you invoke this magic to place regular, non-compiled files into your compiled assemblies?  It all starts, as many things do, in Visual Studio.  With the file added to your project, take a gander at the file’s properties  (by pressing F4 if you don’t have some crazy keyboard setup).  The property page for a file is rather short, and we need to look no farther than the first property of the file to accomplish our purpose.  The Build Action property of the file is usually set to Content, meaning “[the] file is not compiled, but is included in the Content output group.”  By changing this to Embedded Resource, “[the] file is embedded in the main project build output as a DLL or executable.”  This is most of the magic we need, but we’re not quite done yet.

    Since this is a JavaScript file that we’ll want to access on the web, we need to tell the assembly that it’s okay to give the web access to the file.  If you miss this step, you’re going to get an error page instead of JavaScript when you try to reference it (and everything that depended on the script will be throwing errors both left and right).

    So, to accomplish this, we need to add an assembly-level attribute.  These usually live in the AssemblyInfo file, which, by default, is under the Properties folder of your project.  After adding a using/Imports of System.Web.UI, we can add the following WebResource attribute to our project (given a default namespace of “Engage.Demos.EmbeddedResources” and the file “swfobject.2.1.js” placed in a “JavaScript” folder):

        [assembly: WebResource("Engage.Demos.EmbeddedResources.JavaScript.swfobject.2.1.js", "text/javascript")]
    
    

    So, this brings us to the issue of naming.  The file is assigned a name when it is embedded into the assembly.  This name is the default namespace of the project, plus the folder path, plus the file name itself, all joined together with periods.  One important thing to note is that VB.NET assemblies do not have default namespaces (they have root namespaces, a source of continual confusion and frustration for C# programmers who switch back and forth).  Therefore, the resource names just start with the folder path.  In this case, the name would be "JavaScript.swfobject.2.1.js".

    Now that we’ve handled that, how do we actually get the script on the page?  The same way you get any script on the page, you use the ClientScriptManager (you do use the ClientScriptManager to manage your client scripts, right?).  This is accessible through the page’s ClientScript property, from which you can call RegisterClientScriptResource (when using an embedded resource, you don’t get to choose whether it is a “startup script” [added at the bottom of the page] or “script block,” [added at the top1] it’s always a “block”, so you can’t use the ClientScriptManager if you want to use this technique for a “startup script”).

    The RegisterClientScriptResource method takes a Type and the name of the resource.  The Type, as far as I can tell, is just used to provide an additional “namespace” for the include, i.e. trying to include the same resource twice with the same Type will result in only one script being added to the page, but if they have different Types specified, the script’ll be added twice.  So, in our case (working with a class called DemoPage), this looks like the following call during the page’s Load event:

        this.Page.ClientScript.RegisterClientScriptResource(typeof(DemoPage),
    "Engage.Demos.EmbeddedResources.JavaScript.swfobject.2.1.js");

    One notable time where this won’t work is when you are trying to add a script to the page during a partial postback.  In truth, you’ll probably have the same issue whether you’re embedding your JavaScript or not, but either way, the fix is to move from the ClientScriptManager to the ScriptManager (great naming for these two, don’t you think?).  For embedded scripts, use the static RegisterClientScriptResource method like you would with a ClientScriptManager instance, otherwise, pick between RegisterClientScriptBlock, RegisterClientScriptInclude, and RegisterStartupScript.  When using the ScriptManager methods, you’re required to add a line to the end of your script to notify the manager control that you’re script is done loading, but if your script is embedded into the assembly, it can do it for you and you’ll never have to wonder why the stupid thing isn’t working the way (you think) it’s supposed to work.

    If you have a case where you want to add a reference to an embedded script but the provided methods don’t work for you, you can use the ClientScriptManager.GetWebResourceUrl method to get a URL to that resource that you can use anywhere URLs are accepted, worldwide.

    Now, finally, what about files that aren’t JavaScript?  What if you have an XML schema that you want to load up to valid XML against?  Or what if you have an image that you want to add as a watermark to user-uploaded pictures?  In those cases, when you really want to load up the file within memory, rather than expose it to the web, the workflow is rather different.  At the most basic level, you’re going to use Assembly.GetManifestResourceStream to get a stream of the file.  The easiest way to get an assembly reference is to start with the type you’re in, and then access its Assembly property, like so:

        using (var schemaStream = typeof(DemoPage).Assembly.GetManifestResourceStream(typeof(DemoPage), 
    "Schemas.DemoSchema.xsd"))
    {
    // validate something...
    }

    One further note about naming.  In this situation, the first Type parameter is used to lend its namespace to the resource, so, in this case, since the full name of DemoPage is Engage.Demos.EmbeddedResources.DemoPage, I don’t have to specify the Engage.Demos.EmbeddedResources part of the resource’s name. 

    From there, you can go wild, doing whatever you want to do with the stream of your file.

    Hopefully this clears some things up, and hopefully introduces you to a tool that you may not have known about or may not have known enough about.  Make sure and let me know if you have any questions, or see any better ways to go about this stuff.

     


     

    1 The ClientScriptManager actually only messes with the form element on the page, so it’s really the top and bottom of the page’s form that’s where the scripts get placed. 

  • Compile Error Message HTML

    Server Error in '/dotnetnuke_2' Application.

    Compilation Error

    Description: An error occurred during the compilation of a resource required to service this request. Please review the following specific error details and modify your source code appropriately.

    Compiler Error Message: BC30451: Name 'Initialize' is not declared.

    Source Error:


    Line 81: 
    Line 82: ' stop scheduled jobs
    Line 83: Initialize.Init(app)
    Line 84:
    Line 85: ' log APPLICATION_END event

    Source File: C:\Inetpub\wwwroot\DotNetNuke\Website\App_Code\Global.asax.vb    Line: 89


    Show Detailed Compiler Output:

    Show Complete Compilation Source:

    Version Information: Microsoft .NET Framework Version:2.0.50727.1433; ASP.NET Version:2.0.50727.1433

  • Packaging Modules for DotNetNuke 5

    We just released Engage: Events, and realized that our [DNN] 4 compatible package might not work exactly as we'd like when used in [DNN] 5.  If you're a [DNN] module developer, you probably already know that there have been a ton of changes to the module installer in 5.0.  It will still accept the old module packages, but you'll miss out on a lot if you don't provide an updated package.

    So, the first thing I noticed when testing Events in [DNN] 5 was that it seemed like some of our shared base assemblies weren't being updated to the latest version that comes with Events.  Looking into it further, I realized that those assemblies were associated with the other Engage module I had installed on the site, Engage: Locator.  The short story is that [DNN] now keeps track of assemblies when you install them, to help keep assemblies from being uninstalled or overwritten incorrectly.  When you install a [DNN] 4 module, it appears that it sets the version of the assemblies that it installs to the version of the module.  So, I had installed Locator 1.4, which gave Engage.Dnn.Framework.dll a version of 1.4.0 in the [DNN] database.  When I installed Events 1.1, [DNN] thought it was an older version of Engage.Dnn.Framework.dll (1.1.0), even though it was actually the most recent version.

    So, to help fix this as much as we could, I decided it was time to figure out this 5.0 packaging stuff and create a 5.0 package for Engage: Events.  First, I asked [DNN] to make a module package of Events, based on the [DNN] 4 package I had installed.  I looked through that and updated and removed parts to get it closer to what we wanted.  Specifically, I removed the <eventMessage> section from the Module component, since... I couldn't figure out any reason we'd need that.  I think it might be necessary now if you implement IUpgradeable, but it's best to try it out for yourself.

    I also removed all of the files other than scripts, cleanup, and assemblies, since we use a Resource File .zip to avoid having to change the manifest whenever the content files in the module change.  In [DNN] 4 manifests, this looked like a <resourceFile> element in the <folder> element for your module, pointing to the file to unzip with all of your files.  In [DNN] 5, that didn't work.  After a bit of searching, I found Erik's blog post, DotNetNuke 5 Extention Packaging.  This helped my a ways along my path, showing that these Resource File .zips have their own section in the manifest file.  After a bit of trying to figure out why his example wasn't working for me, I figured out that he'd left out one of the elements (the interior <resourceFile>) in the XML.  With that figured out, my section looks like this:

    <component type="ResourceFile">
      <resourceFiles>
        <basePath>DesktopModules/EngageEvents</basePath>
        <resourceFile>
          <name>Resources.zip</name>
        </resourceFile>
      </resourceFiles>
    </component>

    Back to the first issue I had experienced, I could now specify my own versions for the assemblies that I included in the package.  This isn't necessarily a magic bullet, though, until all of our modules have a [DNN] 5 package.  This is because the correct version of some of the shared assemblies (e.g. Engage.Dnn.Framework.dll) is still lower than the version of some of our [DNN] 4 modules.  (For example, the version of Engage.Dnn.Framework.dll released with Events is 1.1.0, but the latest version of Locator is 1.4.0).  So, if those are installed, we'll see the same inability to overwrite the shared assemblies with newer versions.  But, this is the first step towards getting there.  Accurate information is better than inaccurate, in my book.

    The next change to be made to the generated manifest is to fill in the <owner> section at the top of the package.  We can supply a <name>, <organization>,<url>, and <email> that is shown when the module is installed.  This makes sure people know who wrote the module and how to find us.  From Erik's post, I also found out that you can include HTML in those fields, so you can make the <url> and <email> links, rather than plain text.  Our section looks like this:

    <owner>
      <name>Engage Software</name>
      <organization>Engage Software</organization>
      <url><![CDATA[<a href="http://www.engagesoftware.com/">http://www.engagesoftware.com/</a>]]></url>
      <email><![CDATA[<a href="mailto:support@engagemodules.com">support@engagemodules.com</a>]]></email>
    </owner>

    The last bit of new functionality in the manifest that I worked with is the License and Release Notes.  These are new fields that users see when they install the module.  We were already including a copy of the license in our [DNN] 4 packages, but now we can link to that file and make sure that users explicitly agree to it before installing the module.  We also keep release notes on our website that we can now show upon install, rather than making folks search for them.

    So, at this point, I think I'm done with this process.  However, I try to install, and see the following:

    Tracking this down, I realized that I had deleted elements with default values in the <moduleControls> section of the manifest.  I didn't want to have a <viewOrder> element making it seem like someone was specifically setting that value, when really we don't care, it's just a default.  Well, one of the elements I deleted was <controlKey> from the default module control.  This is how our [DNN] 4 manifest looks, since the default module control doesn't have a key assigned.  Looking at the error, it looked like that was no longer an acceptable practice, so I added a <controlKey/> to that <moduleControl> entry, and it worked like a charm.  And, really, that makes sense.  I care that <controlKey> is empty, I'm not just accepting the default value for lack of a better value, I'm actively choosing to use an empty <controlKey>.

    So, those are the hassles, issues, and features that I encountered while creating a [DNN] 5 package for our module.  Hopefully this'll give you some better understanding of some of what's involved, and get you more quickly around those obstacles that I ran up against.

    [Cross-posted from http://www.engagesoftware.com/Blog/EntryId/194/Packaging-Modules-for-DotNetNuke-5.aspx]

  • Engage: Events 1.1

    The first major update for Engage: Events has been released!  Engage: Events is the event management module for [DotNetNuke] from Engage Software.

    Many of the users of the 1.0 version of Events wanted to display a small, simple list of events in a prominent place on their site, like the home page.  To address this scenario, we created three new templates that look great in a smaller pane, and present a simpler view of the events.

    We also recognized that many of our users needed to have access to the list of attendees outside of the website itself.  The responses list is now exportable to Excel or as a comma separated values (CSV) file.

    One of the more common requests from our users is to limit the number of registrants to an event.  This is especially important when you are scheduling classes or other activities with limited seating.  You can now set a registration cap for your events, and, if you'd like, provide a custom message to the user if they want to register after the event has filled.

    We've put a lot of care and love into this release, and we hope that it makes your event management simple, fun, and more effective.  Take a test drive with the new features, let us know what you think and what you'd like to see next, and then buy it for your site.
  • Understanding Module Isolation in DotNetNuke

    Also known as "Why Did My Skin Just Change?" and "Why Doesn't NavigateURL work?"

    When navigating to between controls within your [DotNetNuke] module, you have many options (and would do well to read Michael Washington's Module Navigation tutorial on the subject).

    If you decide to use the built-in navigation mechanisms, when you want to navigate to your control with the key "EditItems", you'd get the URL by calling EditUrl("EditItems") (or one of its overloads, depending on what other information you need on the querystring).  This method returns a new URL with three pieces of information on it: the current page (tabId), the current module (mid), and the control key your provided (ctl).

    Here's where we encounter module isolation.  When "mid" is on the querystring,  [DNN] loads only that module on the page.  If you try to avoid this by using the NavigateURL method instead of EditUrl, [DNN] won't know which module to load the control for, so it won't load anything.

    In addition to not showing the other modules that are usually on that page, one of the biggest side effects of module isolation that you'll notice is that your skin and container change.  When in module isolation, the admin skin and container are used (though, starting in [DNN] 5.0, there is no longer an admin skin, so this shouldn't be an issue going forward starting in [DNN] 5.0, these are called the edit skin and container, rather than "admin").  If you have a different admin skin, this can be quite jarring to you and your users.

    If the consequences and side effects of module isolation work for you/your customers/your users, then it is definitely the easiest method of getting around your module's controls.  However, it has its drawbacks.  If you need more control, again, check out Michael Washington's Module Navigation tutorial.