In an earlier tip I disagreed with one of Microsoft's recommendations for handling exceptions. I figure I'm on a roll, so here's another objection to some Microsoft advice on handling errors.
In Microsoft's reference documentation for the Exception object's ToString method, Microsoft recommends using Exception object's ToString method to report errors. The Remarks section of the documentation says that the exception's ToString method "returns a representation of the current exception that is intended to be understood by humans."
That's true if, I guess, by "humans," you actually mean "developers."
Let me make my point. Here's what you'll find in the DivideByZeroException object's Message property:
Attempted to divide by zero.
Here's what's returned by the ToString method:
System.DivideByZeroException: Attempted to divide by zero. at CustomerManagement.CreditManagement.CreditManagement_Load(Object sender, EventArgs e) in C:\\Users\\peter\\Source\\Repos\\Customers\\CustomerManagement\\CreditManagement.cs:line 30
Don't get me wrong: I think that the output of the ToString method is a great thing to put into your application's log file. I also think that inflicting that on a defenseless user is just mean. I think what's in the Message property is what you should give to the user.
Having said all that, however, I'm not suggesting you ignore Microsoft's advice on the Exception object's ToString method. The Remarks also say "The default implementation of ToString obtains the name of the class that threw the current exception, the message, the result of calling ToString on the inner exception, and the result of calling Environment.StackTrace." Precisely because it does make a good log file entry, I think you should be making sure that's what the ToString method does when you create your own custom exception object.
Posted by Peter Vogel on 06/03/20190 comments
I hate creating two or more Views that share a lot of HTML because it doubles my maintenance burden by forcing me to keep the two Views in sync. The usual reason I'm doing this is because I have single page that's supporting two user roles and I need to suppress or include some HTML based on the current user's authorization level.
While I try to avoid adding code to a View because I can't automate testing that code, I've been known to put code in Views to conditionally generate HTML to support multiple roles in a single View. The logic is usually pretty simple (a call to some IsInRole method), but it's mixed in and scattered through the HTML that makes up the View.
ASP.NET Core gives me a new tool to use that allows me to tailor a single View for multiple purposes while centralizing my code: The IgnoreSection method. The IgnoreSection method, in a View, allows me to tell the layout View to not pick up a specific section.
For example, I can now put all my command buttons in a section of my View, like this:
@section UpdateButtons {
<input type="submit" value="Update" name="Update"/>
}
Presumably, the layout View used by this View would incorporate the section into the page with a line like this:
@RenderSection("UpdateButtons", false)
But I want this View to be used both by guests (who aren't allowed to update data) and employees (who are). In ASP.NET Core, I can now suppress the section from being displayed by the layout View by passing the name of the section to the IgnoreSection method.
For example, this code in my View ensures that the UpdateButtons section won't be sent to the user if the user is in the "guest" role:
@if (User.IsInRole("guest")) {
@IgnoreSection("UpdateButtons")
}
What I like about this is that I can put this logic in the code block at the top of my View instead of scattering it throughout my View. I can also now control what each user sees just by moving HTML in and out of the appropriate sections or by adding and removing sections in my if statement.
There's also a new IgnoreBody method that suppresses a layout View's RenderBody method, but I'm not clear when I'd want to use that. You may have a better imagination than I do, of course.
Posted by Peter Vogel on 05/31/20190 comments
I have to admit that I've never really understood regular expressions. But if there was any reason that I was going to learn to use them, it would be when doing searches in Visual Studio.
For one thing, regular expressions are easy to invoke when doing a search: When you press Ctrl_F to get the Find dialog box, all you have to do is click the asterisk (*) to the left of the of scope dropdown list to start using regular expressions in your search.
I think, for example, I'm ready to start using the period (which matches a single character) in my searches. That would let me (for example) search for any string that begins with a and ends with p that has only one character in between (a.p). I not only understand this, but it's also easy to extend: Looking for a and p with two characters in between is a..p.
Where I could see being even more successful is by using Replace in Files more. When you open Replace in Files there's a Use Regular Expressions option tucked in under Find Options. Checking off that option enables the button at the end of the "Find what" text box. I like that button because clicking on it produces a quick study guide for regular expression newbies like me. I could do this.
I mean: it's certainly possible that I could learn something new. Unlikely, but possible.
Posted by Peter Vogel on 05/30/20190 comments
You want to put together an error message that includes information from the Customer object that caused the problem. Since you're not repeatedly modifying a single string variable, using StringBuilder would be overkill. You still have three options.
Your first option is the most obvious: Concatenation. Here's some code that assembles a message from constant string and some values:
string msg = "Customer " + cust.Id + " has an invalid status " + cust.Status;
Your second option is to use the string class's Format method. You're probably used to seeing this syntax because there are lots of functions that use string.Format under the hood to build strings. With the Format method, you provide your constant string as your first parameter. Inside that string you put place holders (marked with curly braces: { }) where the variable data is to be inserted. The Format method will insert the values from the following parameters into the place holders.
Here's my message with the Format method:
string msg = string.Format("Customer {0} has an invalid status {1}.", cust.Id, cust.Status);
Your third option is string interpolation. This is the newest mechanism and is what all the "hip and happening" developers use. It looks much like using the Format method, but the placeholders now hold the values to be inserted into the string. You need to flag the string with a dollar sign ($) to tell C# that the curly braces are to be treated as placeholders rather than included in the string:
string msg = $"Customer {cust.Id} has an invalid status {cust.Status}.";
Posted by Peter Vogel on 05/29/20190 comments
When I pressed F5 to start debugging and Visual Studio found a compile-time error, nothing irritated me more than the dialog box Visual Studio popped up that asked, "There were build errors. Would you like to continue and run the last successful build?"
Let me be clear: No, I didn't want to run "the last successful build." I never wanted to run "the last successful build." Who in the world would want to run "the last successful build?" Like any other rational human being in the world, I wanted to run the version of the code with the changes I had just finished making ... well, after I fixed the compile errors, I mean.
So I turned that idiot message off.
If you also want to get rid of that message, then go to Tools | Options | Projects and Solutions | Build and Run. In the right-hand panel under "On Run, when build or deployment errors occur," change the selected item in the dropdown list to Do Not Launch. Now, when you have build errors, Visual Studio will just sit there. You'll have to get into the habit of checking your Error List to find out why you're not in debug mode but, for me, that didn't take me very long.
Posted by Peter Vogel on 05/08/20190 comments
Microsoft has some "best practice" advice to share on how to handle exceptions (a topic I've discussed elsewhere). The Microsoft article is well worth reading ... but there's one piece of advice that I disagree with (talk about hubris, eh?).
One of Microsoft's recommended practices is that you should prefer to throw a built-in Exception class rather than your own, custom Exception class. The problem I see with this is that the built-in Exception objects return, at best, the technical reason for the exception (for example, "Division by zero"). Creating your own exception object allows you to specify the business reason for the failure (for example, "Customer has no sales orders").
Personally, I think that the message with the business reason is more useful both to the user faced with the message and the developer who has to fix the problem. Instead, I recommend using a custom exception object and tucking the original Exception into your custom Exception object's InnerException property. That gives you the best of both worlds.
Posted by Peter Vogel on 04/26/20190 comments
In an earlier post, I discussed the three objects that Microsoft has provided for calling Web Services: HttpWebRequest, WebClient and HttpClient. At the time, I suggested WebClient was the simplest solution, unless you wanted to take advantage of HttpClient's asynchronous processing.
I've reconsidered that choice since then and I'm currently using HttpClient almost exclusively. Part of the reason is that HttpClient gives me the ReadAsAsync method. To understand why I like that method so much, you have to compare it to the alternatives.
Here's how to get a list of Customer objects out of the response from a Web Service using the traditional ReadAsStringAsync method:
HttpClient hc = new HttpClient();
HttpResponseMessage customersRm = await hc.GetAsync(url);
string customersString = await customersRm.Content.ReadAsStringAsync();
List<Customer> custs = JsonConvert.DeserializeObject<List<Customer>>(customersString);
Now here's the code using ReadAsAsync (even the method name is shorter!):
HttpClient hc = new HttpClient();
HttpResponseMessage customersRm = await hc.GetAsync(url);
List<Customer> custs = await customersRm.Content.ReadAsAsync<List<Customer>>();
The only problem is that you don't, in .NET 4.5 or later, get ReadAsAsync without some work -- you'll have to add the Microsoft.AspNet.WebApi.Client NuGet package to your project to pick up this extension method. I think that's worth doing.
Posted by Peter Vogel on 04/25/20190 comments
The biggest change in handling Views in ASP.NET Core is that you can now have more than one _Layout.cshtml file in your project and your Views will pick the one "closest to them." In ASP.NET MVC, you set a View's Layout property to the full path name of the Layout file to be used, like this:
@{
Layout = "~/Views/Shared/_Layout.cshtml";
}
In ASP.NET Core, you can set the Layout property to as little as the name of the file. This, for example, is perfectly OK:
@{
Layout = "_Layout";
}
When you don't provide a path name to your layout View, ASP.NET Core first goes looking for the layout in the same folder as the View that requested it. Only when ASP.NET Core doesn't find a file with the right name "locally" does ASP.NET Core go on to the /Shared folder to find a "global" layout View.
This means that you can set the Layout property in your _ViewStart.cshtml file to a default value "_Layout" but have Views in different folders pick up different layouts. This can mean a lot fewer Views having to override that default setting for the Layout property: If all the Views for a Controller share a layout View, you can just put that layout View in the Views folder for that Controller's Views.
Posted by Peter Vogel on 04/04/20190 comments
In an earlier column, I showed how to access configuration settings in your project's appsettings.json file and then make those settings available throughout your application as IOptions objects.
But you don't have to use the appsettings.json file if you don't want to -- .NET Core will let you hard-code your configuration settings or retrieve them from some other source (a database, perhaps). Wherever you get your settings from, you can still bundle them up as IOptions objects to share with the rest of your application. And your application will neither know nor care where those configuration settings come from.
Typically, you'll retrieve your settings in your project's Startup class (in the Startup.cs file), specifically in the class's ConfigureServices method. If you're not using appsettings.json, creating an IOptions object is a three-step process.
First, you'll need to define a Dictionary and load it with keys that identify your configuration settings and values (the actual settings). That code looks like this:
var settings = new Dictionary<string, string>
{
{"toDoService:url", "http://..."},
{"toDoService:contractid", "???"}
};
For my keys, I've used two-part names with each part separated by a colon (:). This lets me organize my configuration settings into sections. My example defined a section called toDoService with two settings: url and contracted.
The second step is to create a ConfigurationBuilder, add my Dictionary of configuration settings to it and use that ConfigurationBuilder to build an IConfiguration object. This IConfiguration object is similar to the one automatically passed to your ConfigureService method except, instead of being tied to the appsettings.json file, this one is tied to that Dictionary of settings.
Here's that code:
var cfgBuilder = new ConfigurationBuilder();
cfgBuilder.AddInMemoryCollection(settings);
IConfiguration cfg = cfgBuilder.Build();
The fourth, and final, step is to add an IOptions object to your application's services collection from your own IConfiguration object:
services.Configure(cfg.GetSection("toDoService"));
Now, as I described in that earlier article, you can use that IOptions<toDoSettings> object anywhere in your application just like an IOptions object built from your appsettings.json file.
Posted by Peter Vogel on 03/26/20190 comments
In many of these tips, I've suggested ways that you might want to change Visual Studio's default configuration. That's not always a good thing. For example, I've known some developers who, because of some problem, had to re-install Visual Studio and lost all their customizations. I sometimes find myself at a client's site, working on a computer that isn't mine and looking foolish because some customization I depend on is gone ... or I used to, at any rate.
The solution to both problems is some preventative maintenance: Export your Visual Studio settings to a vssettings file. You can then restore those settings in the event of a disaster or moving to a new machine.
To export a file, from the Tools menu, select "Export selected environment settings" and then click the Next button. On the next page, by default, all settings are selected and that's the option I use (I'm concerned that if start picking and choosing settings, I'll leave one of my customizations behind). On this page, therefore, all I need to do is click the Next button.
The third and final page allows me to choose where I'll save the resulting vssettings file. I save it to one of my cloud drives so that I won't lose the file if something happens to my computer.
When I need to set up a new instance of Visual Studio, I import the file and get back my own, personalized version of my favorite development environment. When I'm working at a client's site, I first export the settings on the computer I'm using. I then import my vssettings file from my cloud drive (or a USB on one occasion when I wasn't allowed Internet access).
Posted by Peter Vogel on 03/21/20190 comments
By default, if you add a Razor Page to your project's Pages folder, the URL that you use to access that page is based on the Page's file name. So, for example, a Page with the file name CustomerManagement.cshtml is retrieved with the URL http://<server name>/CustomerManagement. There is an exception to this: A Page with the file name Index.cshtml is retrieved with just http://<server name>. This convention extends to subfolders: If that CustomerManagement.cshtml file is in the Pages/Customers folder then its URL is http://<server name>/Customers/CustomerManagement (and Index.cshtml in the same folder has http://<server name>/Customers as its URL).
I have two problems with this. First, the URL http://<server name>/Customers is the same for both a Page in a file named Customers.cshtml directly in the Pages folder and a Page in a file called Index in the Pages/Customers folder. You may say that this problem is one I've created for myself but I'd describe it as an accident waiting to happen ... and, when it does happen, it's a problem that can only be fixed by renaming or moving files. Of course, when you rename or move files, bookmarks all over the world stop working.
This is related to my second problem: Both the names of the files where I keep my code and their location on my Web server's hard disk should be private -- they're nobody's business but my own. Integrating file names and folder locations into your application's UI is the exact opposite of loose coupling, just like incorporating class and method names into your UI.
Fortunately, you have two options to implement loose coupling between URLs and Pages. The option I like is to use the page directive at the top of your Page's cshtml file: Just provide it with a string with an absolute URL that you want to use to access the page.
For example, this line, inside my CustomerManagement.cshtml file, means that the URL for my Page is now http://<server name>/Customers/Manage:
@page "/Customers/Manage"
Alternatively, you can use the AddRazorPagesOptions method when configuring MVC support in your Startup class. This code also establishes http://<server name>/Customers/Manage as the URL for my CustomerManagement Page:
services.AddMvc().AddRazorPagesOptions(options =>
{
options.Conventions.AddPageRoute("/CustomerManagement",
"Customers/Manage");
});
I need to point out, with either of these options, the RedirectToPage method must continue to use the Page's file name:
return RedirectToPage("CustomerManagement");
Posted by Peter Vogel on 03/15/20190 comments
I never get my code right the first time. And, even after my code passes all its tests, it's still not right. That's because I will have learned a lot about the problem when writing my code (wouldn't it be awful if that didn't happen?). But, unfortunately, much of my code reflects decisions made in an early, more ignorant stage of this learning process. As a result, I typically want to take some time, after the code passes its tests, to rewrite my code and make it "better."
The problem is that my clients need some proof that this rewrite is time well spent. One way to do that is to use Visual Studio's Analyze | Calculate Code Metrics menu choice to generate some hard numbers that show how the code is getting "better."
But, as I tell people all the time, no metric makes sense by itself: You need to compare your code's current numbers to what you had before to see if things are getting (in some sense of the word) "better." What you want to do is save your original numbers so you can compare them to your later, "better" numbers.
You have two ways to do this. One way is, in the Code Metric Results window, just select the metrics you're interested in, right-click on them and select Copy. Now you can paste these metrics into any place you want to keep them -- Excel would be a good choice. Of course, if you're doing that, why not just pick the Open List in Excel option on the Code Metric Results' toolbar? Now you can save those results in a workbook for later reference.
Heck, now that you've got those number in Excel, you can create a graph from them. My clients love graphs.
Posted by Peter Vogel on 03/13/20190 comments