tag:blogger.com,1999:blog-15219116988100389482024-03-06T05:04:29.369+01:00tseoUnknownnoreply@blogger.comBlogger7125tag:blogger.com,1999:blog-1521911698810038948.post-68780220651415328082013-03-27T21:47:00.003+01:002013-08-23T10:36:35.692+02:00Customize default ASP.NET validator javascript functionsThese days we want some fancy css to be applied client side when the user entered some invalid data on a form. The good thing about ASP.NET validators is that they also validate server side. The bad thing is that you can only specify some text to be displayed when the entered data is invalid.<br />
<br />
Well, we are about to change that! Let’s start with changing the validator evaluation function.<br />
<br />
<pre class="brush: js">$(function () {
for (var i = 0; i < window.Page_Validators.length; i++) {
// Create a new property and assign the original evaluation function to it
window.Page_Validators[i].baseEvaluationFunction = window.Page_Validators[i].evaluationfunction;
// Set our own validation function
window.Page_Validators[i].evaluationfunction = evaluateField;
}
});</pre>
<br />
With the help of jQuery, this will run when the DOM is ready. It loops over the ASP.NET validators on the page and creates a new property ‘<span style="font-family: Courier New, Courier, monospace;">baseEvaluationFunction</span>’ for each of them on the fly. That property is assigned with the current (default) evaluation function. Secondly, we assign our custom evaluation function to the ‘<span style="font-family: Courier New, Courier, monospace;">evaluationfunction</span>’ property.<br />
<br />
<pre class="brush: js">function evaluateField (validator) {
// Run the original validation function
var isvalid = validator.baseEvaluationFunction(validator);
// Handle the result
if (isvalid) {
clearError(validator);
} else {
setError(validator);
}
// Return result
return isvalid;
}</pre>
<br />
In the custom evaluation function the base (default) evaluation function is executed and based on the result things on the form can be changed. In the functions <span style="font-family: Courier New, Courier, monospace;">setError</span><span style="font-family: inherit;"> </span>and <span style="font-family: Courier New, Courier, monospace;">clearError</span> the correct css is applied. I used the errormessage property to also explain the user what is wrong. Finally the function returns if the evaluation was valid or not.<br />
<br />
One more thing: set the <span style="font-family: Courier New, Courier, monospace;">Display</span> property of the ASP.NET validators to <span style="font-family: Courier New, Courier, monospace;">None</span>, otherwise ASP.NET will still display the error message.<br />
<br />
<pre class="brush: html"><asp:RequiredFieldValidator ID="FirstNameValidator" ControlToValidate="FirstNameTextBox" Display="None" ErrorMessage="Please enter your first name." runat="server"></asp:RequiredFieldValidator></pre>
<br />
Pretty easy, eh?Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-1521911698810038948.post-68701474708712966522012-09-19T08:35:00.002+02:002012-09-19T08:48:56.971+02:00Handle regular html controls in ASP.NET WebformsBy "handle regular html controls in .NET" I mean handling values of non-ASP.NET controls, nor html server controls after a postback. Basically an input control with no <span style="font-family: Courier New, Courier, monospace;">runat="server"</span> attribute. Why on earth would one do such a thing? Well, recently I was working on a project where input fields could be added kinda indefinitely in a certain form. The fields were added to the form by using javascript and templates. We preferred that over a postback per added field.<br />
<br />
So, in our html template we had something like this:<br />
<pre class="brush: html"><input id="MyRegularTextBox_" name="MyRegularTextBox_" type="text" value="" /></pre>
<br />
This was added to a container div and a counter was added to the id and name of the input field. In the end, a form could look like this when it was posted back to the server:<br />
<pre class="brush: html"><input id="MyRegularTextBox_1" name="MyRegularTextBox_1" type="text" value="" />
<input id="MyRegularTextBox_2" name="MyRegularTextBox_2" type="text" value="" />
<input id="MyRegularTextBox_3" name="MyRegularTextBox_3" type="text" value="" /></pre>
<br />
As ASP.NET doesn't know about these fields, we need another way to access the values of those added field. In fact, if you are from the old asp era or know some php or the like, it's pretty obvious. You just need to loop over all the keys in the <span style="font-family: Courier New, Courier, monospace;">Request.Form</span> object. Combine that with a LINQ expression and we have a winner!<br />
<pre class="brush: csharp">foreach (string key in Request.Form.AllKeys.Where(x => x.StartsWith("MyRegularTextBox_")))
{
string myRegularTextBoxValue = Request.Form[key];
// Do some great stuff!
}</pre>
<br />
The same logic can also be applied for file uploads. Instead of looping over the <span style="font-family: Courier New, Courier, monospace;">Request.Form</span> keys, you need to loop the <span style="font-family: Courier New, Courier, monospace;">Request.Files</span> collection. However, one important thing to do is setting the encoding type of the form.
<br />
The html looks like this:
<br />
<pre class="brush: html"><input type="file" id="attachment_1" name="attachment_1" /></pre>
<br />
Code behind like this:
<br />
<pre class="brush: csharp">protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
// The form must handle file uploads (with regular html, non-asp.net, controls)
Page.Form.Enctype = "multipart/form-data";
}
foreach (string key in Request.Files)
{
HttpPostedFile postedFile = this.files[key];
}</pre>
<br />
Et voilà, you're set to go!<br />
<br />Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-1521911698810038948.post-11154472170954637702011-12-23T16:11:00.002+01:002011-12-23T16:24:57.262+01:00.NET Entity Framework: Detect if an Entity is newWhen I'm using Entity Framework (EF) I'd like to have a Save() method that I can call without having to know what the state of the object is. I don't care if the object should be inserted or updated, that's up to the Save() method to find out.<br />
<br />
In Entity Framework you can find out the state of an object by calling <span style="font-family: 'Courier New', Courier, monospace;">context.ObjectStateManager.GetObjectStateEntry(obj)</span>. To be able to use this method, your object needs to be attached to the context. If not, it will raise an <span style="font-family: 'Courier New', Courier, monospace;">InvalidOperationException</span>. And that will be the case if you are saving an object that was just created (and not attached yet).<br />
<br />
So what I do is using an extension method to determine what to do in the Save() method:<br />
<br />
<pre class="brush: csharp">public static bool IsNew(this EntityObject entity)
{
return entity.EntityKey == null || entity.EntityKey.IsTemporary;
}</pre>
<br />
If an object isn't attached yet, its EntityKey will be null. Otherwise, if it's attached but new, its EntityKey will be indicated as temporary.<br />
<br />
The Save() mehtod calls the IsNew() method and acts accordingly.<br />
<pre class="brush: csharp">private void Save(Product product)
{
if (product.IsNew())
{
Insert(product);
}
else
{
Update(product);
}
}
</pre>Unknownnoreply@blogger.com0tag:blogger.com,1999:blog-1521911698810038948.post-37884886551539453402011-09-15T09:56:00.000+02:002015-10-09T14:06:56.752+02:00Installing Windows 8 Developer Preview in VMwareSeveral versions of Windows 8 Developer Preview are public available on Microsoft’s <a href="http://msdn.microsoft.com/en-us/windows/apps/br229516" target="_blank">Windows Developer Preview downloads</a>. Just pick the one that fits for you. The downloaded file is an ISO file. You don’t need to burn it on disc.<br />
<br />
If you don’t have a spare computer where you can install Windows 8 Developer Preview on, you can install it on your own machine using VMware. First you’ll need <a href="http://downloads.vmware.com/d/info/desktop_end_user_computing/vmware_workstation/8_0" target="_blank">VMware Workstation 8</a> or <a href="http://downloads.vmware.com/d/info/desktop_end_user_computing/vmware_player/4_0" target="_blank">VMware Player 4</a>. The latter one is free<strike>, but at this moment you can only get it by downloading VMware Workstation 8</strike>. Don’t worry if you don’t have a license, you don’t need one because we will only use the free VMware Player 4. If you want full functionality you can <a href="https://www.vmware.com/tryvmware/?p=vmware-workstation8&lp=1" target="_blank">download a trial version</a>. For both downloads you’ll need to create an account. Go ahead and download and install VMware 8 if you have not already.<br />
<br />
In this little tutorial I will use VMware Player, but the steps are nearly the same for VMware Workstation. Start VMware Player and click “Create a New Virtual Machine”. In the window that pops up, make sure you choose “I will install the operating system later.”<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjC_J31t9tfYInqC4ybKRe7ewLj21EsPk7gH1XWXkdjVLKV_bGZ8qbxIn7flcC-f8YExEiIf3G7Vh4e2AgOAlzK-FH-i2qW7EVjDpzboShvK14VTXVj4eiV8mtrzQiDBu-h9OzXmBmenYQ/s1600-h/Installing-Win8-VMware-1%25255B4%25255D.png"><img alt="Installing-Win8-VMware-1" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgBgyTKSRsCdgqMuploR_3miJqUKIJwWX1RvQTt_MIKHDhWSQ-5adjQiqsGoYpx9ZVtvJFj1SHqz7QIZWzhEUG5EdIIHJqF4k0analTw9OPf02BJyqkVW24tIoxzKMPhBlDbUaLNwvlvOE/?imgmax=800" height="334" style="border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto;" title="Installing-Win8-VMware-1" width="432" /></a><br />
<br />
If you select one of the other options, you’ll get in trouble when Windows 8 Developer Preview is installing. Because in one of the next screens of the VMware wizard you’ll be asked to enter the license key for Windows 8 Developer Preview which you don’t have. You can leave it empty, but when Windows 8 Developer Preview is installing you’ll get the following error: “Windows cannot read the <ProductKey> setting from the unattend answer file”.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0PMi8GAZaU2Ifw_vyQZxLjDahyphenhyphen8MVBkQVycA93Pj__LM1rV0DjkhsmDpqhMMBrvdpnGNZ-UIBtiYjO95UcD3VNH1Y65YLzEh1OWsv0mlAtLECYc4xkZJXwY7h2w358K2lZ5WNEbcNjCc/s1600-h/Installing-Win8-VMware-2%25255B3%25255D.png"><img alt="Installing-Win8-VMware-2" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1V5n1COg9Cf1HEnoowko6Ih5IR0tME_pdb9FR_4vzg7c9UiJOP20nc4lGhYlSMFtxeHcrBjM3WiGnci56SrtuNu5Wht3pTQs972vBdXSgA8UW4RMZbwSa1rwCrfjCeQ93WEh5TJBK_6k/?imgmax=800" height="180" style="border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto;" title="Installing-Win8-VMware-2" width="498" /></a><br />
<br />
So, let’s proceed with the last option and click “Next >”. Depending on which version you have downloaded, select “Windows 7” or “Windows 7 x64” as operating system. I have downloaded the 64-bit version, so I chose “Windows 7 x64”.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBagVr50DkX4hTmTscjhdmRpQE7DFx72Kmg9wHH2rxNJ4oelDK0YrJo_TOrVuovp3yEMROCs4xQhV3esN6X6KLwxIPHPTyeMPvdBVKEAdx1-eZLuWmrwBjdbs_6BZ3Z2daN75nkJtJw_M/s1600-h/Installing-Win8-VMware-3%25255B3%25255D.png"><img alt="Installing-Win8-VMware-3" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiBkLrgovPiciNLtfsbMSPqnWwIBLbCXCCaGGt4bYASdlvHe6GcO_rixr6TtwLQhcsnHYs192uwX_OLYFWT8-Du841Ors13hIgnq2bptyOOkilRf9j7h0vW4R5cpHU0ECuAWzlt6EREMc0/?imgmax=800" height="334" style="border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto;" title="Installing-Win8-VMware-3" width="432" /></a><br />
<br />
Enter a Virtual machine name in the next step, for instance “Windows 8 Developer Preview”, and set the location of your virtual machine. I did not change anything in the next steps of the wizards, but you can choose the maximum disk size (default 60GB), whether to split the virtual disk or not. If you want to adjust the default allocated memory (1GB) you’ll need to click on “Customize Hardware…” in the last step to change it. This is my summary screen:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_rQeCYQCRxeGMm4RpXZKaIGTJhmOX0dbk3ovQwYBIkq6_4L_iIuPBZI8x37vAtDltMdyj-qMbGcMRHr0E4v97W-z2N2iW9ss6ycxPwm9P0RjduQg31zobk68PBfO77vM7hGeEVTvTB2U/s1600-h/Installing-Win8-VMware-4%25255B3%25255D.png"><img alt="Installing-Win8-VMware-4" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgvfJzJzDwGELVEKs-2xAvMRlRbGSI1lruP3x37aO8RDNat0V-MmJn3KNV1iCcQQI-y_Yz2RR9wTLvgEfmKrlBFCKUS6w1hKTLM34EQjT3sDfBFASQLbPtm1Fa6jNfEHQWJnDg4LFQc_Ac/?imgmax=800" height="334" style="border-bottom: 0px; border-left: 0px; border-right: 0px; border-top: 0px; display: block; float: none; margin-left: auto; margin-right: auto;" title="Installing-Win8-VMware-4" width="432" /></a><br />
<br />
Click “Finish” and select the created virtual machine in VMware Player. Open the Virtual Machine Settings by clicking “Edit virtual machine settings” and select “CD/DVD IDE” in the device list. Make sure “Connect at power on” is checked and that you point it to the downloaded ISO file.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjSgSwc7GMTpjHmAWtM74v_m5tCRFG1iT1TJV28gh0iEZyfHDhJnSYszRTUhYpyogXMy4jSxAvoY4eXxlV6LxUVaC0rFJVdLFkiPRnW15K_kc2nOWmtMTOILT2xqK_G-AV-McULTUawOZg/s1600-h/Installing-Win8-VMware-5%25255B3%25255D.png"><img alt="Installing-Win8-VMware-5" border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHRt4SrQvdLXs55Pf_LUs8EQL8QFkoIprgACO6hjOE9b8ljG44xDfWWUOwLpm9cx8yDHYb439dh-gc3uins7k1ODPtzr5iakdoKUFqNM7kc0AzS6iDFtCsgdmfBUvtCqU2pGM2iBM0qVc/?imgmax=800" height="505" style="border-bottom-color: initial; border-bottom-style: initial; border-bottom-width: 0px; border-left-color: initial; border-left-style: initial; border-left-width: 0px; border-right-color: initial; border-right-style: initial; border-right-width: 0px; border-top-color: initial; border-top-style: initial; border-top-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto;" title="Installing-Win8-VMware-5" width="640" /></a> <br />
<br />
Click “OK” and power on the virtual machine. The Windows 8 Developer Preview installation wizard will start. At the end you can enjoy Windows 8 Developer Preview! One more tip: the metro style apps will only run if your screen resolution is at least 1024x768 (hardware or virtual machine).<br />
<br />
<b>UPDATE 1</b> (2 jan 2012): Some people have still trouble installing Windows 8 Developer Preview. You can fix that by removing the floppy of the virtual machine: Power off Windows 8 Virtual Machine, go to Virtual Machine Settings and remove the floppy drive. Thanks for the tip abkar, John and Claudiu!<br />
<br />
<b>UPDATE 2</b> (2 jan 2012): Modified links to download page for VMware Player.<br />
<br />
<br />Unknownnoreply@blogger.com47tag:blogger.com,1999:blog-1521911698810038948.post-81610932803643747102010-12-13T20:39:00.000+01:002010-12-13T20:39:05.671+01:00Using macros in Visual Studio to rename files and classesRecently I was working on a project that was initially started back in 2005. The solution is divided in several layers: entity layer, business layer, data layer and UI. For the first three layers <a href="http://www.codesmithtools.com/" target="_blank">CodeSmith</a> is used to generate base classes based on the database schema. Extra functionality is added by implementing derived classes. Every database change or new common functionality can be added for all classes by changing a CodeSmith template. Very handy if there are more than 100 objects per layer.<br />
<a name='more'></a><br />
There was one drawback, however. The templates generated the files in the several layers with the same name. So a table named “Pet”, would create three “Pet” classes: one in the entity layer, one in the business layer and one in the data layer. On top of that, the derived classes had the same filename, “Pet.cs” (located in a different directory). So when coding or debugging, all tabs in Visual Studio had the same label “Pet.cs”, very confusing! My objective was to change the filenames and classes in such a way that the class renames are refactored in the whole solution and that the file renames are reflected in MS Visual Source Safe. My solution: a macro!<br />
<br />
So go to “Tools > Macros > New macro project” to create a macro project. Macros are written in VB. In the macro project I created the several methods, one for each layer and some common methods. It’s pretty self explanatory. The RenameSymbol() method causes the rename of the class to be reflected in all of the code!<br />
<br />
<pre class="brush: vb">Sub RenameBussinessLayer()
For Each project As Project In DTE.Solution.Projects
If project.Name = "BusinessLogic" Then
For Each item As ProjectItem In project.ProjectItems
If item.Name = "Generated" Or item.Name = "Implementation" Then
RenameBLClasses(item.ProjectItems)
End If
Next
End If
Next
End Sub
</pre><pre class="brush: vb">Sub RenameBLClasses(ByVal items As ProjectItems)
For Each item As ProjectItem In items
item.Open()
Dim doc As Document
doc = item.Document
If doc.Path.Contains("BusinessLogic") Then
RenameClass(doc, "BL")
End If
Next
End Sub
</pre><pre class="brush: vb">Sub RenameClass(ByVal doc As Document, ByVal layer As String)
Dim element As CodeElement
Dim newName As String
Dim postFix As String
element = GetClassElement(doc.ProjectItem.FileCodeModel.CodeElements)
If layer = "DAL" Then
postFix = "Repository"
newName = element.Name & postFix
ElseIf layer = "BL" Then
postFix = "Manager"
' class name is already OK, only filename needs to change
newName = element.Name
ElseIf layer = "OM" Then
postFix = element.Name
' class name is already OK, only filename needs to change
newName = element.Name
End If
' Rename file
If Not doc.ProjectItem.Name.EndsWith(postFix & ".cs") Then
doc.ProjectItem.Name = newName & ".cs"
End If
' Rename class
If Not element.Name.EndsWith(postFix) Then
Dim elementToRename As CodeElement2
elementToRename = DirectCast(element, CodeElement2)
elementToRename.RenameSymbol(newName)
End If
End Sub
</pre><pre class="brush: vb">Private Function GetClassElement(ByVal elements As CodeElements) As CodeElement
For Each element As CodeElement In elements
If element.Kind = vsCMElement.vsCMElementImportStmt Then
Continue For
ElseIf element.Kind = vsCMElement.vsCMElementClass Then
Return element
Else
Return GetClassElement(GetElementMembers(element))
End If
Next
Return Nothing
End Function
</pre><pre class="brush: vb">Private Function GetElementMembers(ByVal element As CodeElement) As CodeElements
Dim elements As EnvDTE.CodeElements
If TypeOf element Is EnvDTE.CodeNamespace Then
elements = CType(element, EnvDTE.CodeNamespace).Members
ElseIf TypeOf element Is EnvDTE.CodeType Then
elements = CType(element, EnvDTE.CodeType).Members
ElseIf TypeOf element Is EnvDTE.CodeFunction Then
elements = DirectCast(element, EnvDTE.CodeFunction).Parameters
End If
Return elements
End Function
</pre>Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-1521911698810038948.post-20471641528038467702010-08-06T14:32:00.013+02:002010-08-06T14:39:24.403+02:00log4net ColoredConsoleAppender: log levels and its problemsI was playing around with the ColoredConsoleAppender of log4net. It allows you to display a certain log level in another color. For instance messages with an error level can be displayed red in the console. But I encountered some problems with it.<br />
<br />
<a name='more'></a>To display error messages in red and debug messages in yellow, the appender configuration will look like this:<br />
<pre class="brush: xml"><appender name="ColoredConsoleAppender" type="log4net.Appender.ColoredConsoleAppender">
<mapping>
<level value="ERROR" />
<forecolor value="Red" />
</mapping>
<mapping>
<level value="DEBUG" />
<forecolor value="Yellow" />
</mapping>
<layout type="log4net.Layout.PatternLayout">
<conversionpattern value="%date [%thread] %-5level - %message%newline" />
</layout>
</appender>
</pre><br />
In your code, you’ll have something like this:<br />
<br />
<pre class="brush: csharp">ILog logger = LogManager.GetLogger("Test");
logger.Info("Info message");
logger.Debug("Debug message");
logger.Error("Error message");
</pre><br />
This generates the following output:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinqnvgHkRqEqd-ZEpapUjKFd36bgiHW6mtaI_2z9g7XK1VAyGILXFLBr639KPFYaseg5zPnU4iUDfuURgKaTofKslzgfnqBm2DAIM2b2TBoZJb28WpzAf_THDRZ6lhL2Y-ilZB6xGkM1o/s1600-h/image%5B13%5D.png"><img alt="log output" border="0" height="133" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj9oe2jruw509r9lYAxaJPUTVs2W-l7vWdMpi19E9qTYp93fxKGjNUgWXVXfF0wkVF7JOd4XV-kgiiV2zTrrnjdfS-mxczQjkIckyciE19kjVJcHenpbs3yvXr33q4lVJ-0U6YKxCAN-d0/?imgmax=800" style="border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline;" title="log output" width="461" /></a> <br />
<br />
As you can see, the info message is also in yellow although there is no color defined for the info level. I expected that the info message would have the default console color. You may have noticed that the colors of my console window are different than the default ones. It is blue text on white background. I prefer a bright background. The output has a black background, even though I did not specify a background color in the ColoredConsoleAppender configuration.<br />
<br />
The project I was working on had already a reference to the source of log4net. I’ve made already some changes to <a href="http://tseonet.blogspot.com/2010/07/making-log4net-run-on-net-40.html">make log4net run on .NET 4.0</a>, so why not change – or should I say fix? – the behavior of the ColoredConsoleAppender?<br />
<br />
Having set a breakpoint at the beginning of the Append() method in the log4net.Appender.ColoredConsoleAppender class, I stepped through the code to see what was happening. Eventually I noticed the following line of code in the Append() method:<br />
<br />
<pre class="brush: csharp">// see if there is a specified lookup
LevelColors levelColors = m_levelMapping.Lookup(loggingEvent.Level) as LevelColors;
//
//
</pre><br />
I went to the definition of that Lookup() method (it’s in the log4net.Util.LevelMapping class) and saw the following comment:<br />
<br />
<span style="font-family: Courier New;">Lookup the value for the specified level. Finds the nearest mapping value for the level that is equal to or less than the level specified.</span><br />
<br />
For colors, I want an exact match and not the nearest (or less) one. So I changed the Lookup() method to call a new overloaded method. The overloaded method has a boolean parameter which tells the method to look for an exact level match (true) or to find it by the default behavior (false). To preserve the default behavior and to not break existing code, the original Lookup() method calls the new overloaded method with ‘false’ for the boolean parameter.<br />
<br />
<pre class="brush: csharp">public LevelMappingEntry Lookup(Level level)
{
return Lookup(level, false);
}
public LevelMappingEntry Lookup(Level level, bool exactMatch)
{
if (m_entries != null)
{
foreach (LevelMappingEntry entry in m_entries)
{
if ((exactMatch && level == entry.Level) || (!exactMatch && level >= entry.Level))
{
return entry;
}
}
}
return null;
}
</pre><br />
Back in the ColoredConsoleAppender class, I changed the line with the Lookup() call as follows:<br />
<br />
<pre class="brush: csharp">// see if there is a specified lookup
LevelColors levelColors = m_levelMapping.Lookup(loggingEvent.Level, true) as LevelColors;
//
//
</pre><br />
With these changes to the code, the output looks like this:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiurROWJ7-pLPw3zVizOWyXRA2gxuT1HP9BnJfS3bFNYmAg_eDdzQHXPhkO1pZAVQOpZQs1Iv2OsYHNIxEK8ndAIQVWr51KlJDkkJN_PPHoV3xfe3IlOke9kWh0ur4g-g2SuI5NgNu_Vqk/s1600-h/image%5B14%5D.png"><img alt="image" border="0" height="111" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhIYBaO0giYTB7wyYk0X_4NYvdO_1J914Jj64DdpHh9n80fOZpzYx7Z8bCzk6Fy4AULlTL-qWC22r7Nr4TrjdQtCy_n5AA1BiLwmcubM7vjTyTp0frEvHUtaj4OST_hV_9rVOW9JyERJ8Q/?imgmax=800" style="border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline;" title="image" width="452" /></a> <br />
<br />
The info message is not in yellow anymore, but it still doesn’t respect my default colors of the console because it’s white text on a black background! Something else is still wrong, let’s debug the Append() method again. The following lines got my attention:<br />
<br />
<pre class="brush: csharp">// Default to white on black
ushort colorInfo = (ushort)Colors.White;
</pre><br />
Like comment says log4net assumes that white on black is default. To fix this issue, several lines of code must be changed. You can find the complete changed Append() method here below. I hope the comments should be enough for you to understand the changes that are made. All changes are marked with a "//CHANGE" comment.<br />
<br />
<pre class="brush: csharp">override protected void Append(log4net.Core.LoggingEvent loggingEvent)
{
if (m_consoleOutputWriter != null)
{
IntPtr consoleHandle = IntPtr.Zero;
if (m_writeToErrorStream)
{
// Write to the error stream
consoleHandle = GetStdHandle(STD_ERROR_HANDLE);
}
else
{
// Write to the output stream
consoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
}
//CHANGE: Read the current console info here instead of later
// get the current console color - to restore later
CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
GetConsoleScreenBufferInfo(consoleHandle, out bufferInfo);
//CHANGE: Do not assume white on black is default
// Default to white on black
//ushort colorInfo = (ushort)Colors.White;
//CHANGE: Read the color attributes of the console info
// wAttributes:
// - More info at http://msdn.microsoft.com/en-us/library/ms682088(v=VS.85).aspx#_win32_character_attributes
// - bit 1 to 4: forecolor
// - bit 5 to 8: backcolor
ushort colorInfo = (ushort)((int)bufferInfo.wAttributes & 255);
//CHANGE: Lookup with exact match
// see if there is a specified lookup
LevelColors levelColors = m_levelMapping.Lookup(loggingEvent.Level, true) as LevelColors;
if (levelColors != null)
{
colorInfo = levelColors.CombinedColor;
}
// Render the event to a string
string strLoggingMessage = RenderLoggingEvent(loggingEvent);
//CHANGE: Moved up in order to get console default color
// get the current console color - to restore later
//CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
//GetConsoleScreenBufferInfo(consoleHandle, out bufferInfo);
// set the console colors
SetConsoleTextAttribute(consoleHandle, colorInfo);
// [skip]A long comment about WriteConsoleW[/skip]
char[] messageCharArray = strLoggingMessage.ToCharArray();
int arrayLength = messageCharArray.Length;
bool appendNewline = false;
// Trim off last newline, if it exists
if (arrayLength > 1 && messageCharArray[arrayLength-2] == '\r' && messageCharArray[arrayLength-1] == '\n')
{
arrayLength -= 2;
appendNewline = true;
}
// Write to the output stream
m_consoleOutputWriter.Write(messageCharArray, 0, arrayLength);
// Restore the console back to its previous color scheme
SetConsoleTextAttribute(consoleHandle, bufferInfo.wAttributes);
if (appendNewline)
{
// Write the newline, after changing the color scheme
m_consoleOutputWriter.Write(s_windowsNewline, 0, 2);
}
}
}
</pre><br />
Having changed that, the output is like this:<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhbsWAFRq-wIX7xoU_v0MYSImHgJjxhqpFJ1x8YZSPPudRA4kweLU5iTMyfJvKz_UEKFsCB-iYzc1IhvtABIZlU-tbUzxJ7UPNQNnFMy2UZ24DlvI8UxkRjgt-2Q-NeGNerEoCd68V4jeQ/s1600-h/image%5B18%5D.png"><img alt="image" border="0" height="120" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhMOsz53l-hb2Ezk9wdKqF3t1Eg7ZkG8zfTRrFXNwmtfXWUtgiYZaAOJgt7HkSsynCkOQkmFIVhBSYB7mzjKZETu4ITTxiHw5hWDEAQnMWJya6x60AYuHLy0vINYKAcHjoX5BLPKmSdktI/?imgmax=800" style="border-bottom-width: 0px; border-left-width: 0px; border-right-width: 0px; border-top-width: 0px; display: inline;" title="image" width="460" /></a><br />
<br />
One more thing that is still not correct: the background color of the debug and error levels. I did not specify a background color, so it should be white instead of black. Changing that would mean significant changes in the inner class LevelColors because the default color of the background is set as black. That class should then know about the default colors of the console. A workaround is to specify also a background color when specifying a forecolor and I’m willing to accept that workaround for now! However, if you would feel the need to investigate this issue, please do so and let me know.Unknownnoreply@blogger.com1tag:blogger.com,1999:blog-1521911698810038948.post-34761682227717932892010-07-01T20:59:00.006+02:002010-09-15T18:51:41.954+02:00Making log4net run on .NET 4.0I was playing around with .NET 4.0 and wanted to include logging. So I <a href="http://logging.apache.org/log4net/download.html">downloaded log4net</a> (v1.2.10) and added the source project to my solution. The next thing to do was to configure log4net and I would be able to log to one or several 'appenders'. At least, that was what I thought! How to configure and use log4net is not the scope of this article, but you can find all what you need on the <a href="http://logging.apache.org/log4net/index.html">log4net site</a>. A summary of the solution can be found at the bottom of this article.<br />
<div><br />
<a name='more'></a>When building I got some errors & warnings. One of these warnings was:<br />
<br />
<span style="font-size: 85%;"><span style="font-family: 'courier new';">Could not resolve assembly "System.Web". The assembly is not in the currently targeted framework ".NETFramework,Version=v4.0,Profile=Client". Please remove references to assemblies not in the targeted framework or consider retargeting your project.</span></span><br />
<br />
I went googling about the different target frameworks and I found a page explaining the this: <a href="http://msdn.microsoft.com/en-us/library/cc656912.aspx">http://msdn.microsoft.com/en-us/library/cc656912.aspx</a>. Basicly there are some parts "missing" by default if the target is ".NET Framework 4 Client Profile".<br />
<br />
OK, fair enough, I changed the target framework to ".NET Framework 4" instead of ".NET Framework 4 Client Profile" on the log4net project.<br />
<br />
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiife_bN7buMs7KBRcIwXQ8gYKZRDUEWYf0T8XEAuZyXEqLK4WKlv3vvb9NEQYvutuXEjCT5F1dIHzAOsETbaLSoZ8GjduWaOyzdgh8LQsscOW0vhNqD8RF4RO-QGdum24xzUD7PK3bDbQ/s1600/Target+framework.PNG" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"><img alt="" border="0" id="BLOGGER_PHOTO_ID_5488896234340566914" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiife_bN7buMs7KBRcIwXQ8gYKZRDUEWYf0T8XEAuZyXEqLK4WKlv3vvb9NEQYvutuXEjCT5F1dIHzAOsETbaLSoZ8GjduWaOyzdgh8LQsscOW0vhNqD8RF4RO-QGdum24xzUD7PK3bDbQ/s320/Target+framework.PNG" style="cursor: pointer; display: block; height: 107px; margin: 0px auto 10px; text-align: center; width: 301px;" /></a><br />
Finally, I can build my solution! I was dissapointed again when I tried to run it. Now I get the following error in log4net.Core.DefaultRepositorySelector.CreateRepository():<br />
<br />
<span style="font-family: 'courier new'; font-size: 85%;">Exception has been thrown by the target of an invocation.</span><br />
<br />
The inner exception was more detailed:<br />
<br />
<span style="font-size: 85%;"><span style="font-family: 'courier new';">Inheritance security rules violated while overriding member: 'log4net.Util.ReadOnlyPropertiesDictionary.GetObjectData(System.Runtime.Serialization.SerializationInfo, System.Runtime.Serialization.StreamingContext)'. Security accessibility of the overriding method must match the security accessibility of the method being overriden.</span></span><br />
<br />
It states that something is wrong with the overriden method <span style="font-family: 'courier new';">GetObjectData()</span> in the <span style="font-family: 'courier new';">ReadOnlyPropertiesDictionary</span> class. <span style="font-family: 'courier new';">ReadOnlyPropertiesDictionary</span> implements <span style="font-family: 'courier new';">ISerializable</span> which makes you have to implement <span style="font-family: 'courier new';">GetObjectData()</span>. When you look at the definition of <span style="font-family: 'courier new';">ISerializable.GetObjectData()</span> you'll see there is an <span style="font-family: 'courier new';">SecurityCritical</span> attribute specified on it. With the inner exception in mind, I added the <span style="font-family: 'courier new';">SecurityCritical</span> attribute to the method <span style="font-family: 'courier new';">GetObjectData()</span> in <span style="font-family: 'courier new';">ReadOnlyPropertiesDictionary</span>. To be honest, the people in this thread pointed me in the good direction: <a href="http://stackoverflow.com/questions/2903669/log4net-and-net-framework-4-0">http://stackoverflow.com/questions/2903669/log4net-and-net-framework-4-0</a>.<br />
<br />
Having changed that I ran it again. Now other errors arised, however Visual Studio did not break on it, it was written to the VS output window and my console app. One of the errors was the same as above, but then for <span style="font-family: 'courier new';">log4net.Core.LoggingEvent.GetObjectData()</span>. So I added the attribute <span style="font-family: 'courier new';">SecurityCritical</span> there too.<br />
<br />
Still no luck with that, because now this error was thrown:<br />
<br />
<span style="font-size: 85%;"><span style="font-family: 'courier new';">System.MethodAccessException: Attempt by security transparent method 'log4net.Util.SystemInfo.get_CurrentThreadId()' to call native code through method 'System.AppDomain.GetCurrentThreadId()' failed. Methods must be security critical or security safe-critical to call native code.</span></span><br />
<br />
The property <span style="font-family: 'courier new';">CurrentThreadId</span> of the class <span style="font-family: 'courier new';">log4net.Util.SystemInfo</span> calls <span style="font-family: 'courier new';">System.AppDomain.GetCurrentThreadId()</span>. However, it seems that this is obsolete and need to be changed to <span style="font-family: 'courier new';">System.Threading.Thread.CurrentThread.ManagedThreadId</span>. See <a href="http://msdn.microsoft.com/en-us/library/system.appdomain.getcurrentthreadid.aspx">http://msdn.microsoft.com/en-us/library/system.appdomain.getcurrentthreadid.aspx</a>.<br />
<br />
Changed the property, run it, get an error. Do you have the impression we are in a <span class="short_text" id="result_box"><span style="background-color: white;" title="">vicious circle? Don't worry, so do I, but we are getting out of it!<br />
<br />
This time the error is:<br />
<br />
<span style="font-size: 85%;"><span style="font-family: 'courier new';">System.MethodAccessException: Attempt by security transparent method 'log4net.Appender.ColoredConsoleAppender.ActivateOptions()' to call native code through method 'log4net.Appender.ColoredConsoleAppender.GetConsoleOutputCP()' failed. Methods must be security critical or security safe-critical to call native code.</span></span><br />
</span></span><br />
Some security changes were made in the .NET 4.0 framework. In the <span style="font-family: 'courier new';">AssemblyInfo.cs</span> file of the log4net project, you'll find the following line:<br />
<span style="font-size: 85%;"><br />
<span style="font-family: 'courier new';">[assembly: System.Security.AllowPartiallyTrustedCallers]</span></span><br />
<br />
This is affected by the security changes. Take a look at this page for more details: <a href="http://msdn.microsoft.com/en-us/library/system.security.allowpartiallytrustedcallersattribute.aspx">http://msdn.microsoft.com/en-us/library/system.security.allowpartiallytrustedcallersattribute.aspx</a>.<br />
<br />
I commented that line and now I can succesfully run my application with the logging capabilities of log4net! Isn't that great?<br />
<br />
Because the <span style="font-family: 'courier new';">AllowPartiallyTrustedCallers </span>is in comment, the <span style="font-family: 'courier new';">SecurityCritical</span> attributes we added can be removed again.<br />
<br />
As a summary, this is what you'll need to do to make log4net run on .NET framework 4:<br />
<ul><li>Include the source as a project</li>
<li>Change <span style="font-family: 'courier new';">log4net.Util.SystemInfo.</span><span style="font-family: 'courier new';">CurrentThreadId</span> to return <span style="font-family: 'courier new';">System.Threading.Thread.CurrentThread.ManagedThreadId</span> instead of <span style="font-family: 'courier new';">System.AppDomain.GetCurrentThreadId()</span>.</li>
<li>Put <span style="font-size: 85%;"><span style="font-family: 'courier new';">[assembly: System.Security.AllowPartiallyTrustedCallers] </span></span>in comment in the AssemblyInfo.cs file.</li>
</ul>That's it! Please do leave some comment or feel free to ask a question. I'll be happy to respond to it.<br />
<br />
</div>Unknownnoreply@blogger.com20