by Oliver
20. March 2013 15:24
While setting up a specification tests project for our new TeamReview tool, I was facing an HTTP 500.19 error when hosting our site in IIS Express. There are lots of questions on stackoverflow concerning this error, Microsoft has a whole page on it, but there is a whole bunch of suberrors that this error addresses. Error 0x8007007b: Cannot read configuration file Unfortunately, none of the above mentioned links contained or solved the specific error code I was seeing: Error Code 0x8007007b Config Error Cannot read configuration file Config File \\?\C:\Projects\_teamaton\teamreview\TeamReview.Specs\bin\Debug\..\..\..\TeamReview.Web\web.config After some reading, trying, fiddling, it appeared to me that maybe the path to the config file somehow messed up IIS Express. I admit that it was at least a bit unusual to use the parent directory dots. But it came from my test harness code where I wanted to use relative paths and used Path.Combine() to do that: var webPath = Path.Combine(Environment.CurrentDirectory, "..", "..", "..", "TeamReview.Web");
Pitfall: .. in path
Well, it turns out IIS Express didn't like it. Once I called it with a cleaned up path string, everything just worked:
"C:\Program Files (x86)\IIS Express\iisexpress.exe" /path:"C:\Projects\_teamaton\teamreview\TeamReview.Web" /port:12345 /systray:false
So, watch out for your physical path values when using IIS Express!
Use DirectoryInfo to navigate up your directory tree
To get the correct path without using absolute paths but also avoiding the .. I used the DirectoryInfo class:
var webPath = Path.Combine(
new DirectoryInfo(Environment.CurrentDirectory).Parent.Parent.Parent.FullName, "TeamReview.Web");
by Oliver
19. November 2012 21:09
I’ve just spent much more time than I’d want on figuring out why the following code wouldn’t give me a checked checkbox, even when I set the ViewModel.Value to true: public class ViewModel {
public string Name { get; set; }
public dynamic Value { get; set; }
}
@Html.CheckBox(Model.Name, (bool?)Model.Value)
The problem is with the signature of CheckBox, of course! Instead of calling the one where the second argument is of type bool, it calls the one where the second arg is of type object!
// the method I thought I was calling
public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, bool isChecked) {
return CheckBox(htmlHelper, name, isChecked, (object)null /* htmlAttributes */);
}
// the method I in fact was calling
public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, object htmlAttributes) {
return CheckBox(htmlHelper, name, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
// the method that's being called by the second method above: isChecked == null!
public static MvcHtmlString CheckBox(this HtmlHelper htmlHelper, string name, IDictionary<string, object> htmlAttributes) {
return CheckBoxHelper(htmlHelper, null, name, null /* isChecked */, htmlAttributes);
}
Even though they were not strictly necessary, still, the framework sources helped me out this time to find this subtle bug (in my code, of course!).
The solution to the problem looks like this:
@Html.CheckBox(Model.Name, (bool)(Model.Value == true))
Happy Coding!
by Oliver
28. August 2012 23:41
Working on Marinas.info, we want to create SEO friendly links to specific searches that will hopefully rank high up in the search engines. They should look something like www.marinas.info/germany for marinas situated in Germany or www.marinas.info/wífi for marinas that provide wifi internet access. We also plan on supporting most European languages, some 30+ or so, even if not from the start. Anyway, the number of URLs we will generate is likely to explode, and we need a solution to consistently generate and handle those thousands of different URLs. This post investigates a few solutions that have popped up. External URL Rewriting solutions – UrlRewritingNet and the IIS Url Rewrite Module The two solutions listed here are called external because they integrate with the ASP.NET MVC request pipeline in way that they will be invisible to the actual web application. Routes would be rewritten in an early step and the application would only get to see the rewritten URL that it would know how to handle. UrlRewritingNet Since 2006, UrlRewritingNet has been around and I assume it’s a widespread solution. We use it in Camping.info, and probably the best part is the easy configuration. Still, I remember that we had a few problems with it that needed fixing, so we would most likely have to use that slightly customized version. I’m not 100% sure if it would work with MVC out of the box since we’ve only used it with ASP.NET WebForms. IIS Url Rewrite Module This is a solution by Microsoft that would certainly get the job done, too. I haven’t really used it yet in any larger project though, so I’d have to dive into it first. If you feel like giving it a go, check out http://learn.iis.net/page.aspx/734/url-rewrite-module/. Internal solutions – Orchard Rewrite Rules and ASP.NET MVC Routing These approaches work inside the application. Orchard Rewrite Rules Orchard Rewrite Rules is an Orchard Module that brings (a subset of) mod_rewrite style URL rewriting to any Orchard installation. We’ve successfully used it e.g. to redirect traffic from http://marinas.info to http://www.marinas.info. The greatest benefit is that it’s just another module that you can simply enable and disable at runtime from within the application without a need to recycle the app pool as would be the case for the two external rewriting solutions since their configuration is coupled to the web.config file. The drawback of this solution is that we’re also new to its syntax and it’s not that clear (yet) how we would efficiently map the thousands of routes with their dynamic values that we are aiming at. ASP.NET MVC Routing It seems that ASP.NET MVC’s built-in routing engine can provide us with what we are looking for. At first glance, looking at the standard examples of route definition, it didn't appear that promising. Remember, we want to support URLs such as www.marinas.info/germany and www.marinas.info/wifi for a lot of search categories. The routes to match those request would either have to be very specific, e.g. one per category, or very general, e.g. one for all categories. The first approach would lead to the thousands of routes I mentioned earlier, the latter would also catch requests for other parts of the application that might have nothing to do with search since it’s so general. It turns out there are more advanced features in ASP.NET MVC Routing that allow to overcome these restrictions. Custom Route Constraint with IRouteConstraint and Custom Route Handler with MvcRouteHandler This answer on Stack Overflow to a question concerning exactly the same problem points to a blog post on writing your own Route Constraint which is a great way to deal with the second problem mentioned above, that a too general route would match too many requests. The custom route constraint could e.g. constrain the application of a certain route to only valid values. Another approach, presented in this post on SEO friendly URLs in MVC, would be to derive from the default MvcRouteHandler and implement some custom logic to handle our custom URLs. Conclusion: ASP.NET MVC Routing has it all I’m glad I set out to examine those different solutions because I totally underestimated the power of ASP.NET MVC Routing if I hadn’t. Another great post on Optimizing ASP.NET MVC3 Routing talks about even more of its aspects and how Stack Overflow handles their large number of routes with high performance using (mostly) built-in functionality. Looks very much like this is the way to go! Happy coding!
by Oliver
22. August 2012 13:48
When we started development on Marinas.info, we decided to write acceptance tests for all important features of our application. This decision was even more justified by the fact that a bunch of similar platforms are to follow using the same codebase. We wanted an application with less bugs and easier maintenance. Writing good, automated acceptance tests is not easy and it’s not fast, either. For some time now, we’ve been trying to get the first set of our tests run green, which proved especially tricky on our TeamCity continuous integration server. This post investigates a working solution. The ingredients: SpecFlow, Coypu (Selenium), Browser, Web Server, and MvcIntegrationTestFramework SpecFlow In .NET world, using SpecFlow to write acceptance tests is nothing new and has recently become, yet again, more appealing after its update to version 1.9. One of our scenarios for verifying image upload functionality looks like this: It’s simple to write, easy to read and great living documentation. For Browser based tests you need: Coypu (Selenium) Everyone who has written tests for Selenium for even a mildly ajax-y site knows how painful it can be to create reliably working tests. Coypu alleviates the pain and makes test creation as straight-forward as it should be in the first place. Coypu is: A robust wrapper for browser automation tools on .Net, such as Selenium WebDriver that eases automating ajax-heavy websites and reduces coupling to the HTML, CSS & JS A more intuitive DSL for interacting with the browser in the way a human being would A few examples of Coypu’s clean API can be seen here in one of the step definitions for the above scenario (Browser is an instance of the BrowserSession class from Coypu): A web browser To run browser based tests you, of course, need … a browser! Coypu offers support for quite a bunch of them, including the usual suspects Internet Explorer, Chrome, and Firefox. A web server You need to host your application in some web server or another to process requests. Well, this statement turns out to be only partially true, as you will see with the MvcIntegrationTestFramework. But at least for browser based test you need a web server, and you basically have the choice between IIS and IIS Express (if you don’t want to write your own or use someone else’s implementation). We chose IIS Express as it is manageable through a non-administrator account, but it needs to be installed on all machines that will execute the tests. For non-browser based tests: MvcIntegrationTestFramework Introduced by Steven Sanderson in 2009, this small framework allows to write integration tests for ASP.NET MVC applications and execute them without a browser! It empowers you to make assertions on your controllers’ actions’ results rather than on the rendered html output by injecting some clever hooks into your MVC application under test. An example of how a test would look can be found in the above mentioned post. The “magic” of this framework lies in the use of ApplicationHost.CreateApplicationHost() which creates an application domain for hosting your ASP.NET application. Check out this screenshot of part of the source code: How to put the pieces together After a quite radical evolution of our test code (which you can read up on in my follow-up post The Long Road to Browser Based Acceptance Testing), we finally settled for the following: Before the first test starts, setup an instance of the AUT (application under test). This includes: deploying the AUT as we do for our staging environment, but to a temp folder initialize an AppHost instance à la MvcIntegrationTestFramework, i.e. an ASP.NET enabled application domain that hosts the AUT execute the Orchard setup command via the AppHost instance (instead of running the setup through a browser, which we used to do but was a lot slower) Before each test run (SpecFlow scenario) we then execute various commands to setup the environment for the concrete test, e.g.: clean the database simply by overwriting it with a copy we saved after the initial setup create Marina entries that will be displayed and searchable on the site, again, using the AppHost instance Once we want to execute steps in the browser, we do the following: start an instance of IIS Express pointing to the deployed application (we used the wrapper code from Spinning up IISExpress for integration testing) initiate a Coypu BrowserSession which under the hood creates an instance of the browser you choose after battling with Internet Explorer, Chrome, and Firefox Portable, we now use Firefox 10.0.6 ESR (Extended Support Release) because version 10 is of now the highest version supported by Selenium (2.1.25) and the ESR doesn’t ask to be updated all the time After each test run (SpecFlow scenario) we do this: close the browser shut down the IIS Express instance (we slightly modified the above mentioned wrapper code calling Kill() on the process instance after the call to CloseMainWindow() so that it reliably terminates even on TeamCity) Conclusion Setting up a reliable environment for automatically executing acceptance tests has not been a walk through the park but we finally have a solution that basically “just works”. Hopefully, our experience will help you save a couple of hours and also some headache along the way Happy coding!
by Oliver
2. August 2012 15:19
On Camping.Info, we lately had a user report that hitting the enter key while in the login form one would be redirected to the search page instead of being logged in. This happened when the focus was still on the password text box because the “Search” button on the page was the first input element on the page (inside the single <form> element that WebForms allows for) of type submit which would by default handle the enter key press event. To improve the user experience, we set out to find a way to postback the correct UpdatePanel – the one which currently has the focus (or one of its child controls). Using an ASP.NET Panel and the DefaultButton property There is a built-in solution to this problem if you’re willing to slightly change your markup and add a hidden <asp:Button> element to your part of the page you want to asynchronously update. You need to wrap your existing html into an <asp:Panel> element and set its DefaultButton property to the ID of that hidden button. This would look something like this: 1: <div class="form21">
2: <asp:Panel runat="server" DefaultButton="btnHiddenRegister">
3: <asp:TextBox ID="txtEmail" runat="server" />
4: <asp:TextBox ID="txtPassword" runat="server" TextMode="Password" />
5: <asp:LinkButton ID="btnRegister" runat="server">Register</asp:LinkButton>
6: <asp:Button ID="btnHiddenRegister" runat="server"style="display:none" />
7: </asp:Panel>
8: </div>
Of course, you need to hook up the code-behind Click handler for the btnRegister LinkButton to the hidden Button element as well to make this work.
The proposed solution will instead have html like the following:
1: <div class="form21 jq-form">
2: <asp:TextBox ID="txtEmail" runat="server" />
3: <asp:TextBox ID="txtPassword" runat="server" TextMode="Password" />
4: <asp:LinkButton ID="btnRegister" runat="server" CssClass="jq-form-submit">Register</asp:LinkButton>
5: </div>
Let’s see how we get this to work.
Using jQuery
On stackoverflow.com, I quickly found this post about a jQuery replacement for DefaultButton or Link where the this answer points to the original solution from March 2009 proposed by this post on fixing the enter key in ASP.NET with jQuery. I’ve improved on the proposed answer in a few ways, so this is what this post is about.
The original solution
1: $(document).ready(function(){
2: var $btn = $('.form_submit');
3: var $form = $btn.parents('.form');
4:
5: $form.keypress(function(e){
6: if (e.which == 13 && e.target.type != 'textarea') {
7: if ($btn[0].type == 'submit')
8: $btn[0].click();
9: else
10: eval($btn[0].href);
11: return false;
12: }
13: });
14: });
Remarks: works only if the content is present at the time the script executes, and works only for a single pair of form and form-submit elements.
Our improved solution
1: $('body')
2: .off('keypress.enter')
3: .on('keypress.enter', function (e) {
4: if (e.which == 13 && e.target.type != 'textarea') {
5: var $btn = $(e.target).closest('.jq-form').find('.jq-form-submit');
6: if ($btn.length > 0) {
7: if ($btn[0].type == 'submit') {
8: $btn[0].click();
9: }
10: else {
11: eval($btn[0].href);
12: }
13: return false;
14: }
15: }
16: });
Improvements
The first thing to improve is the registration of the keypress event handler. Since Camping.Info is quite a dynamic site with lots of asynchronous postbacks to the server (mostly using WebForms’ UpdatePanels) a lot of the javascript code we need on our page won’t execute properly as is because it expects the elements it works with to be present at the time of the script’s execution. That’s also the case with the original solution: both the submit button element with class .form_submit and the pseudo-form element .form (I’ve called it pseudo-form element since it’s just a parent element of any type marked with the class .form) must be present when the script executes, otherwise nothing will happen. That’s why – using jQuery 1.7 syntax – we register the keypress event handler NOT on the pseudo-form element but directly on the body using on(). We also the event namespace it by appending .enter to its name keypress so that we can safely remove it (using off()) in case we call the script a second time during postback to prevent it from being attached twice. Please note, that at this point neither the pseudo-form nor the form submit element must be present on the page for this to work.
Once the enter key has been pressed anywhere on the page and we’re inside the event handler, we set out to check for a pseudo-form surrounding the element that received the enter key press event and inside it we look for a submit element. [I’ve also changed the class names for those elements by our internal convention to prefix all those that are jQuery related by jq- to set them apart from the CSS class names our designer works with.] Only if we’ve found both those elements, we proceed with either clicking the element if it’s really a button or executing the javascript code that ASP.NET rendered into our LinkButton’s href tag which asynchronously posts back to the server for some update to our page.
A subtle but important detail here is to return false (and thus prevent the event from being processed any further) only in case we actually found the pseudo-form and submit elements, so that in the opposite case the default behavior of the page will be preserved.
Conclusion
The solution presented here works equally well with static and dynamically loaded page content and for any number of forms on your page. It replaces the solution provided by ASP.NET WebForms’ using a Panel and its DefaultButton property, resulting also in cleaner markup.
Happy Coding!
by Oliver
9. September 2011 22:29
Today I went off to create a custom search module for our new Orchard based web application. I simply copied the module Orchard.Search, renamed all namespaces and such to Teamaton.Search, replaced some strings and prepended a prefix of “Teamaton” to a bunch of them. I wanted the new module to be able to run side-by-side with the original Orchard.Search module to make sure I had an independent implementation and there wouldn’t be any conflicts in case the Orchard.Search module is already installed somewhere. A problem I ran into quite quickly was that I wouldn’t see any of the search index’s fields on the Admin page for the new module (to the right you see the settings in the original Search module which is what I expect to see on the left as well): Searching the discussions on Orchard’s codeplex site, I found this very helpful post on Module site settings not showing up which promised to be the key to my problem. During the initial string replacement orgy, in SearchSettingsPartDriver I had changed return ContentShape(…).OnGroup(“search”) to
return ContentShape(…).OnGroup(“TeamatonSearch”).
Now, this wasn’t in sync anymore with the Group Id specified inside the SearchSettingsPartHandler, but it should be (as the mentioned post suggests). So I changed that to match the Group “TeamatonSearch” like so:
1: protected override void GetItemMetadata(GetContentItemMetadataContext context) {
2: if (context.ContentItem.ContentType != "Site") {
3: return;
4: }
5: base.GetItemMetadata(context);
6: context.Metadata.EditorGroupInfo.Add(new GroupInfo(T("TeamatonSearch")));
7: }
Unfortunately, it still did not work.
Frustration.
Another post on the discussion list led me to believe it was a problem with my Copy’n’Paste. There, piedone states:
One remarkable point is a name clashing with content parts: there can't be content parts with the same name in different modules as this leads to problems with schema creation and also when selecting them in them admin menus (because then their displayed name is also the same).
This somehow made sense to me, so I went and prefixed the part type name with Teamaton. When that didn’t work I also prefixed all properties of all ContentParts and Records because I remembered that I’d encountered a similar problem before when building our Google Maps module which, contained the same Latitude and Longitude property names that another ContentPart had already defined. But still: to no avail.
Desperation.
In the first post mentioned above, Bertrand Le Roy also states:
I have been in that exact situation. This is probably an error getting swallowed somewhere. […] In my case, it was the feature not enabling all the classes I needed, in particular the record, and the system couldn't find how to map database records.
So I went to the Orchard console … feature disable Teamaton.Search … feature enable Teamaton.Search … still nothing.
Next it hit me like a sledge hammer – Migrations.cs! I hadn’t taken a single look at the persistence mapping for the SettingPartRecord! Well, turns out there was something wrong in there, but only since after I prefixed both parts and properties because now they wouldn’t match the strings in Migrations.cs anymore (a good example where magic strings break stuff).
Kind of helpless, I launched NHProf to look at the database calls that were being made and saw that there weren’t any for the Teamaton.Search module – but for the Orchard.Search module there were … hm. I scanned through the project files again – and stared at placement.info, another file I hadn’t touched since copying the original Search module. Bingo! This was the root of all evil or rather of the search fields not displaying. It looked like this:
1: <Placement>
2: <Place Parts_Search_SiteSettings="Content:1"/>
3: <Place Parts_Search_SearchForm="Content:1"/>
4: </Placement>
The problem with it was that I had renamed all the view files and prepended a prefix to them! So the correct version should look like this:
1: <Placement>
2: <Place Parts_TeamatonSearch_SiteSettings="Content:1"/>
3: <Place Parts_TeamatonSearch_SearchForm="Content:1"/>
4: </Placement>
That’s because the views are named TeamatonSearch.SiteSettings.cshtml and TeamatonSearch.SearchForm.cshtml and are both placed inside a Parts folder:
And now I saw the search fields even for our own new search module.
Lesson learned
If you can’t see something check the placement.info if you (accidentally) haven’t forgot to make that something visible And if you still can’t see it – double check!
Happy coding!
by Oliver
4. March 2011 15:18
We definitely have long paths on our client’s platform www.camping.info, for example for a concrete rating on the detail page of a campsite with a long name in a state with a long name - http://www.camping.info/deutschland/schleswig-holstein-und-hamburg/campingplatz-ferien-und-campinganlage-schuldt-19573/bewertung/r23989 - but the path (everything after the .info including the ‘/’) is still only 112 characters long which is still a long way from the 260 character barrier that’s the default in ASP.NET (see the MSDN). The problem Well, the same page in Greek for example has the following URL: http://el.camping.info/γερμανία/σλέσβιχ-χολστάιν-αμβούργο/campingplatz-ferien-und-campinganlage-schuldt-19573/αξιολόγηση/r23989, at least that is what we see in the browser bar. Essentially, this URL will be encoded when requesting the page from the server so it becomes a gigantic http://el.camping.info/%CE%B3%CE%B5%CF%81%CE%BC%CE%B1%CE%BD%CE%AF%CE%B1/%CF%83%CE%BB%CE%AD%CF%83%CE%B2%CE%B9%CF%87-%CF%87%CE%BF%CE%BB%CF%83%CF%84%CE%AC%CE%B9%CE%BD-%CE%B1%CE%BC%CE%B2%CE%BF%CF%8D%CF%81%CE%B3%CE%BF/campingplatz-ferien-und-campinganlage-schuldt-19573/%CE%B1%CE%BE%CE%B9%CE%BF%CE%BB%CF%8C%CE%B3%CE%B7%CF%83%CE%B7/r23989! Now the path of the URL is a whopping 310 characters long! That’s quite a bit over the limit - but even for shorter URLs the cyrillic or greek equivalents surpass the limit not as rarely as one would think once they are URL encoded. The exception you get when exceeding the default of 260 chars is: System.IO.PathTooLongException: The specified path, file name, or both are too long. The fully qualified file name must be lessthan 260 characters, and the directory name must be less than 248 characters.
And this is what the error looks like in Elmah:
The solution
You don’t have to search long to find a solution to this problem on the web: http://forums.asp.net/t/1553460.aspx/2/10. Just make sure the httpRuntime node contains the properties maxUrlLength AND relaxedUrlToFileSystemMapping like so:
<httpRuntime maxUrlLength="500" relaxedUrlToFileSystemMapping="true" />
You might wonder what the relaxedUrlToFileSystemMapping property really does: you can read more on MSDN. In short, if set to true “the URL does not have to comply with Windows path rules” (MSDN).
Happy coding,
Oliver
by Oliver
21. January 2011 20:19
We recently updated one our largest project to use ASP.NET 4.0, and for this matter the new Package/Publish feature including sub-web.configs which is meant to supersede the Web Deployment Project. For a manual deployment there’s a good write-up on the msdn library titled ASP.NET Web Application Project Deployment Overview which shows how and where to set this up. In our case this was not satisfactory because our deployment process is a bit more complicated. We push our changes to a central repository and use JetBrains’ continuous integration server (CIS) TeamCity Professional, which is totally free for our project size, for a continuous integration process. Once TeamCity has pulled and tested the current version, it is supposed to deploy this version to our staging server where we further test the complete site. The key point in an automatic deployment was the management of the different web.config files for the different environments our project is running on. Unfortunately, until yesterday every deployment that included changes to the web.config file – even to the staging server - required a manual step of editing the web.config that live on our staging system (outside of source control!). What we used to do: after a successful build on our CIS we simply copied the web application (files) to our staging server! But as Scott Hanselman wrote: If You're Using XCopy, You're Doing It Wrong! This post inspired us to move along and take advantage of the new possibilities that we were given. In the meanwhile, before switching to .NET 4.0 actually, we also took a shot at the Web Deployment Project way of doing things but never actually got that far as to fully automate the deployment – somehow the setup was not as easy as we hoped. Anyway, we wanted web.config Transforms! So what does our setup look like and what did we want to do? During local development and testing I use a web.config file that talks to a local DB instance and has some more specific settings. To run the web application on our staging server we need to replace certain values or whole sections in the web.config. For this transformation we use the sub-web.config files, one for each build configuration: Now, with all of these web.config files the simple XCOPY deployment we used to use does not work any longer. We need to trigger the web.config transformation on the build server and then deploy the whole application. As easy as this looks using the built-in menus and dialogs in Visual Studio – it took me quite a while to find how to do this in an automated build, more concretely from the command line. After unsuccessfulle skimming stackoverflow.com for a solution I finally tripped over this very informative blog post on publishing a VS2010 ASP.NET web application using MSBuild. Admittedly, the author focuses on how to publish on the local machine as it’s yet a different process but towards the end he posts the solution I was looking for: 1: msbuild Website.csproj "/p:Platform=AnyCPU;Configuration=Release;DesktopBuildPackageLocation=c:\_Publish\stage\Website.zip" /t:Package
This was it! After running this on my machine with my own settings I looked into the folder with the zip file and found the following 5 files:
At first I just wanted to take the zip file, copy it to the staging server, unpack it – done! But then I peaked into it… and deeper… and deeper… and… still deeper… until I finally saw our application files underneath this directory:
This has got to be one of the longest paths I’ve ever seen and used! How would I automate the extraction of web application files from the zip with such a path? I was already seeing myself hacking away on the command line…
But wait: what about those files that appeared next to the zip file? A ci-stage.deploy.cmd and a readme.txt caught my attention – of course, I opened the cmd file first :-D
Well… maybe the readme file gives me a shortcut to understanding this and the rest of the 190 lines:
Looks promising! I convinced myself to give it a shot. So we set up a new configuration in TeamCity with the following settings:
These settings reflect the command line from above with a few minor changes (removed the DesktopBuildPackageLocation and set the /v[erbose] switch to m[inimal]):
msbuild Website.csproj "/p:Platform=AnyCPU;Configuration=Release" /t:Package /v:m
The second step is to use the generated script, ci-stage.deploy.cmd. I recommend to run the script by hand once using the /T switch just to make sure everything looks alright. In our case we found out that the deployment of the package would have deleted a lot of files, most of all images, from our website. This was not what we wanted! After a quick search I found this question on stackoverflow.com: MSDeploy: “Leave extra files on destination” from command line? So I added this switch to the parameters list in the build step configuration as follows:
That’s it! This is all we need on the command line to generate a package that is ready for deployment on the staging server. There are a few more necessary settings, including the DesktopBuildPackageLocation, that can be found in the Package settings window inside the project properties of the web application project:
the DesktopBuildPackageLocation can be set here instead of on the command line
the website and application name on the destination IIS server that this package should be installed to
some more options like excluding debug symbols etc.
These settings are saved in the project file and will be used during generation and deployment of the package.
That’s all I have to say right now.
Happy Coding, Oliver
by Oliver
20. March 2010 14:59
Wenn man denn unbedingt einen String als Lock-Objekt nutzen will und man sich davon überzeugt hat, nicht den Type eines gerade durch die Methode schwirrenden Objektes zu nutzen (das könnte ja jeder machen ;-)), dann sollte man auf jeden Fall an String.Intern(myLockString) denken!
Wo man kann, sollte man also weiterhin einen einfachen object-Lock nutzen:
1: private static readonly object _lock = new object();
Aber man darf dann nicht vergessen, dass alle Threads, die auf den Code im Lock zugreifen von einem einzigen Lock bedient werden. Wenn man die Threads aber in Gruppen aufteilen kann, die unabhängig voneinander arbeiten können, ist diese Beschränkung zu scharf. Ich suchte also einen Weg, einen eindeutigen Lock für jede dieser Gruppen (hier: TranslationGroup) zu generieren, der von allen Instanzen geteilt wird. Der Gruppenidentifikator heißt hier _pageNameKey.
Beispiel:
1: private const string _migrateLock = "9B90360AAF4342798A739DA2D85AF5AF"; // guid
2:
3: private string _pageNameKey = ...; // can be the same in multiple parallel threads
4:
5: public void Migrate(string oldGroupName, Dictionary<string, string> controlIdsOldToNew)
6: {
7: lock (string.Intern(_migrateLock + _pageNameKey))
8: {
9: // do something not here that should not be done in parallel
10: }
11: }
Ließe man in Zeile 7 das String.Intern(…) weg, würde bei jedem Aufruf der Methode ein neues String-Objekt generiert – was für dem Lock seine Existenzberechtigung raubt.
Gruß, Oliver
Technorati-Tags: Multithreading,Lock,String,Intern
by robert
2. December 2009 03:16
Hatte das gar nicht mitbekommen, seit September gibt es eine neue ASP.NET AJAX Version. Änderungen lassen sich hier nachlesen: http://www.asp.net/AJAX/AjaxControlToolkit/Samples/Default.aspx. Auch interessant, das ASP.NET AJAX Toolkit ist jetzt Teil der ASP.NET AJAX Library. Es wurden 20 Bugs gefixed und es gibt ein neues VS Projekt Template für Visual Studio. @Stefan, hätte Dir das geholfen? Neu: Seadragon ist wirklich spannend. Wir könnten das sowohl für Produkt-Bilder als auch für Stellplatzkarten einsetzen. Neu: AsynFileUpload, vielleicht lohnt es bei Gelegenheit mal mit unserer Lösung zu vergleichen. Wobei in Opera der Upload-Button schon nicht funktionierte – gleich ein schlechtes Zeichen. Insgesamt würde ich mal vermuten, dass unsere Lösung in Speak-Lib deutlich besser ist :-)