tag:blogger.com,1999:blog-26527006566348572962024-02-08T16:55:20.652+00:00Just SnippetsArduino, ASP.NET MVC, C#, Delphi, Matlab, NetduinoJim Davieshttp://www.blogger.com/profile/13198336058804859406noreply@blogger.comBlogger10125tag:blogger.com,1999:blog-2652700656634857296.post-9611254217468058302011-07-01T13:39:00.005+01:002011-08-27T11:43:56.280+01:00ASP.NET MVC - Audit Database ChangesYou may wish to create an audit trail of database changes, i.e. creates, updates or deletes of records. This might be for internal corporate reasons (the 'blame game'), or for external regulatory reasons, e.g. FDA Part 11 compliance.<br />
<br />
Using SQL Server or similar, you can setup a combination of triggers and stored procedures to automatically audit each change, but the simple approach presented here is to maintain the audit trail in application server code using a modified <b>DbRepository</b> pattern (as per NerdDinner). Once setup as described below, no other code changes or database changes are required (other than provision of appropriate views / reports etc. of the audit data).<br />
<br />
<h1>Create an Audits Table</h1><br />
For example, using SQL Server:<br />
<br />
<table border="1"><tr> <th>Column Name</th> <th>Data Type</th> <th>Allow Nulls</th> <th>Comments</th> </tr>
<tr> <td>AuditID</td> <td>int</td> <td>no</td> <td>Primary key. Auto-incrementing.</td> </tr>
<tr> <td>AuditNewState</td> <td>nvarchar(MAX)</td> <td>no</td> <td></td> </tr>
<tr> <td>AuditOperation</td> <td>nvarchar(10)</td> <td>no</td> <td></td> </tr>
<tr> <td>AuditPrimaryKey</td> <td>int</td> <td>no</td> <td>This is a foreign key for one of your business tables.</td> </tr>
<tr> <td>AuditTable</td> <td>nvarchar(30)</td> <td>no</td> <td>The name of the business table for this item.</td> </tr>
<tr> <td>AuditTime</td> <td>datetime</td> <td>no</td> <td>The time of the change.</td> </tr>
<tr> <td>AuditUser</td> <td>int</td> <td>no</td> <td>The user instigating this change.</td> </tr>
</table><br />
<br />
<h1>Intercept Each Database Save</h1><br />
If you are using the <b>DbRepository</b> pattern described for NerdDinner, you can simply create a base Repository class for your app, and add a <b>Save</b> method to the base class:<br />
<br />
<pre class="brush: csharp"> public virtual void Save(int user)
{
try
{
if (user == 0)
{
// Don't audit these changes.
DataContext.SubmitChanges();
}
else
{
// Audit these changes.
var deletes = DataContext.GetChangeSet().Deletes;
var inserts = DataContext.GetChangeSet().Inserts;
var updates = DataContext.GetChangeSet().Updates;
DataContext.SubmitChanges();
if (deletes.Count() + inserts.Count() + updates.Count() > 0)
AuditChanges(user, deletes, inserts, updates);
}
}
catch (Exception e)
{
throw e;
}
}
</pre><br />
The <b>Save</b> method is passed some User ID which is saved with the audit item. Passing a user ID of zero will suppress the auditing, for example if you don't want to audit when importing another database.<br />
<br />
The <b>AuditChanges</b> method can be something like:<br />
<br />
<pre class="brush: csharp"> protected virtual bool AuditChanges(int user, IList<object> deletes, IList<object> inserts, IList<object> updates)
{
// Audit multiple change.
var auditRepos = new AuditDbRepository(this);
// Audit the deletes.
foreach (var item in deletes)
AuditChange(item, user, "delete", auditRepos);
// Audit the inserts.
foreach (var item in inserts)
AuditChange(item, user, "insert", auditRepos);
// Audit the updates.
foreach (var item in updates)
AuditChange(item, user, "update", auditRepos);
auditRepos.Save(user);
// OK.
return true;
}
</pre><br />
and the <b>AuditChange</b> method could be:<br />
<br />
<pre class="brush: csharp"> protected virtual bool AuditChange(object item, int user, string operation, IAuditRepository auditRepos)
{
// Audit a single change.
string typeName = item.GetType().Name.ToUpper();
string newState = "";
int primaryKey = 0;
string table = "";
DateTime time = DateTime.Now;
switch (typeName)
{
// Add all business classes to be audited.
case "CUSTOMERS":
table = "Customers";
primaryKey = ((Customer)item).CustomerID;
newState = ((Customer)item).AuditState();
break;
case "PRODUCTS":
table = "Products";
primaryKey = ((Product)item).ProductID;
newState = ((Product)item).AuditState();
break;
}
if (primaryKey == 0)
{
// Ignore this change.
return true;
}
return AuditHelpers.AddItem(time, table, primaryKey, user, operation, newState, auditRepos);
}
</pre><br />
<br />
<h1>Add an Audit Item</h1><br />
Each audited database change causes one new item to be added to the Audits table. A new Audit object is created using the helper method below and added to the Audits repository.<br />
<br />
<pre class="brush: csharp">namespace AuditHelpers
{
public static class AuditHelpers
{
public static bool AddItem(DateTime time, string table, int primaryKey, int user, string operation,
string newState, IAuditRepository repository)
{
var audit = new Audit();
audit.AuditNewState = newState;
audit.AuditOperation = operation;
audit.AuditPrimaryKey = primaryKey;
audit.AuditTable = table;
audit.AuditTime = time;
audit.AuditUser = user;
repository.Add(audit);
// OK.
return true;
}
}
}
</pre><br />
<br />
<h1>Serialize a Record</h1><br />
Each business class to be audited should have an <b>AuditState</b> extension method, probably implemented in a helper class, e.g.<br />
<br />
<pre class="brush: csharp"> public static string AuditState(this Customer cust)
{
// Serialize the current field values of 'cust' for storage in an audit item.
string result = "Name: " + cust.CustomerName + "\r\n";
if (!String.IsNullOrWhiteSpace(cust.CustomerAddress))
result += "Address: " + cust.CustomerAddress + "\r\n";
result += "Tel: " + cust.CustomerTel + "\r\n";
result += "Fax: " + cust.CustomerFax + "\r\n";
return result;
}
</pre>Jim Davieshttp://www.blogger.com/profile/13198336058804859406noreply@blogger.com1tag:blogger.com,1999:blog-2652700656634857296.post-44357123445757976292011-07-01T11:21:00.004+01:002011-07-01T11:30:16.958+01:00ASP.NET MVC - Report Generation from .ASPX FilesYou can easily use the ASP.NET MVC view engines to create static HTML files or custom HTML reports from, for example, .ASPX and .ASCX files - no need to use some other template system!<br />
<br />
Here's a simple example:<br />
<br />
<pre class="brush: csharp"> public string Generate(ViewDataDictionary viewData, string templateFile, string masterFile,
ControllerContext controllerContext)
{
var writer = new StringWriter();
var tempData = new TempDataDictionary();
var viewResult = ViewEngines.Engines.FindView(controllerContext, templateFile, masterFile);
if ((viewResult == null) || (viewResult.View == null))
{
Log.WriteLine("Generate: ERROR - View not found, template file: '{0}', master file: '{1}",
templateFile, masterFile);
return "View not found";
}
var viewContext = new ViewContext(controllerContext, viewResult.View, viewData, tempData, writer);
viewResult.View.Render(viewContext, writer);
return writer.ToString();
}
</pre><br />
The <b>viewData</b> argument is used to pass in your model, e.g.<br />
<br />
<pre class="brush: csharp"> var model = new MyReportViewModel(xxx, yyy);
var viewData = new ViewDataDictionary(model);
</pre><br />
I usually keep the .ASPX and .ASCX fies in a separate subtree of the Views folder.Jim Davieshttp://www.blogger.com/profile/13198336058804859406noreply@blogger.com0tag:blogger.com,1999:blog-2652700656634857296.post-2942551686101878722011-06-30T18:04:00.012+01:002011-07-10T15:55:53.808+01:00Arduino - Triggered Function GeneratorThis is a triggered function generator / pulse generator for the Arduino Mega using Arduino 022.<br />
<br />
The sketch is too long to present as a snippet, but it's downloadable from <a href="http://jacobus.co.uk/snippets/tfg1/tfg1.zip">here</a>.<br />
<br />
<h1>Features</h1><br />
<ul><li>Configurable via commands from the Arduino IDE Serial Monitor.</li>
<li>Triggered by external digital input.</li>
<li>Output sequence includes an optional pulse of configurable duration (in ms) starting at lag 0.</li>
<li>Main output sequence has configurable start time and duration (in ms).</li>
<li>Includes a configurable internal test source which can be looped to the trigger input.</li>
<li>Configuration can be saved to EEPROM.</li>
<li>Automatically loads configuration from EEPROM at startup.</li>
<li>At the moment this design outputs a delayed pulse, but it can easily be extended to output any arbitrary digital waveform (function), i.e. to be a digital AWG (Arbitrary Waveform Generator).</li>
<li>With the addition of provision for analog output, either using PWM functionality or an external DAC, it could be easily extended to make an analog AWG.</li>
</ul><br />
<h1>Commands</h1><br />
The commands currently implemented are:<br />
<br />
<pre> BEEP ON/OFF Switch beep on/off (stimulus input)
HELP Display help information (this command)
INTMODE (string) Set the interrupt mode
(options are LOW / CHANGE / RISING / FALLING)
LAG (int) Set the lag time (ms)
MARKER (int) Set the marker length (ms)
OFF Disable the output
ON Enable the output
OUTPUT (int) Set the output length (ms)
PULSE (int) Set the pulse length (ms)
RESET Reset to default values
RESTORE Restore configuration from EEPROM
SAMPLE (int) Set the sample frequency (KHz)
SAVE Save configuration to EEPROM
STATUS Display status information
TEST (int) Set the test stimulus interval on pin 12 (ms)
</pre><br />
<b>(int)</b> signifies an integer parameter, separated by a space.<br />
<b>(string)</b> signifies a string parameter, separated by a space.<br />
<br />
<h1>Performance</h1><br />
Some screenshots of example output between 50 and 1000 ms lag:<br />
<br />
<a href="http://jacobus.co.uk/snippets/tfg1/50.bmp"><br />
<img src="http://jacobus.co.uk/snippets/tfg1/50.bmp" width="100">50 ms lag</img><br />
</a><br />
<a href="http://jacobus.co.uk/snippets/tfg1/100.bmp"><br />
<img src="http://jacobus.co.uk/snippets/tfg1/100.bmp" width="100">100 ms lag</img><br />
</a><br />
<a href="http://jacobus.co.uk/snippets/tfg1/200.bmp"><br />
<img src="http://jacobus.co.uk/snippets/tfg1/200.bmp" width="100">200 ms lag</img><br />
</a><br />
<a href="http://jacobus.co.uk/snippets/tfg1/500.bmp"><br />
<img src="http://jacobus.co.uk/snippets/tfg1/500.bmp" width="100">500 ms lag</img><br />
</a><br />
<a href="http://jacobus.co.uk/snippets/tfg1/1000.bmp"><br />
<img src="http://jacobus.co.uk/snippets/tfg1/1000.bmp" width="100">1000 ms lag</img><br />
</a><br />
The upper trace is the stimulus input, the positive edge of which triggers both the function generator and the oscilloscope.<br />
<br />
<h1>Description</h1><br />
If anyone's interested, I can add some annotated snippets here describing the code.<br />
<br />
<h1>Acknowledgements</h1><br />
Interrupt usage and tips in <b>speaker_pcm</b> by Michael Smith (michael@hurts.ca).Jim Davieshttp://www.blogger.com/profile/13198336058804859406noreply@blogger.com0tag:blogger.com,1999:blog-2652700656634857296.post-14677305161674734642011-06-30T12:44:00.002+01:002011-06-30T12:46:48.992+01:00C# - String Helpers - Various<h1>Usage</h1><br />
Various short and fairly self-explanatory string helpers.<br />
<br />
<h1>Snippets</h1><br />
<pre class="brush: csharp">using System;
using System.Drawing;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
namespace My.Helpers
{
public class StringHelpers
{
public static int Count(string text, string pattern)
{
// Count how many occurrences of 'pattern' there are in 'text'.
int result = 0;
try
{
int start = 0;
while (true)
{
int index = text.IndexOf(pattern, start);
if (index < 0) break;
result++;
start = index + pattern.Length;
}
}
catch (System.Exception Ex)
{
Log.WriteException("StringHelpers", "Count", Ex);
}
return result;
}
public static string Get(string text, string defaultValue)
{
if (!String.IsNullOrWhiteSpace(text))
return text;
else
return defaultValue;
}
public static string GetFirstLine(string text)
{
string result = "";
if (!String.IsNullOrWhiteSpace(text))
{
int index = text.IndexOf(Environment.NewLine);
if (index < 0)
result = text;
else
result = text.Substring(0, index);
}
return result;
}
public static string GetMD5Hash(string text)
{
var x = new MD5CryptoServiceProvider();
byte[] bs = Encoding.UTF8.GetBytes(text);
// Compute the MD5 hash value.
bs = x.ComputeHash(bs);
// Convert it to a hex string.
var s = new System.Text.StringBuilder();
foreach (byte b in bs)
s.Append(b.ToString("x2").ToLower());
return s.ToString();
}
public static string RegexExtract(string text, string pattern)
{
// Return the first part of 'text' that matches 'pattern' (if any), otherwise an empty string.
try
{
Regex rx = new Regex(pattern);
Match match = rx.Match(text);
if (match.Success)
return match.Value;
}
catch (System.Exception Ex)
{
Log.WriteException("ServiceTester", "RegexMatch", Ex);
}
return "";
}
public static bool RegexMatch(string text, string pattern)
{
// Does 'text' match 'pattern'?
try
{
Regex rx = new Regex(pattern); // , RegexOptions.Compiled | RegexOptions.IgnoreCase);
return rx.IsMatch(text);
}
catch (Exception Ex)
{
Log.WriteException("ServiceTester", "RegexMatch", Ex);
}
return false;
}
public static string Repeat(char ch, int count)
{
return new String(ch, count);
}
public static string Repeat(string text, int count)
{
var builder = new StringBuilder();
for (int i = 0; i < count; i++)
builder.Append(text);
return builder.ToString();
}
public static string Tabify(string text, int tabWidth)
{
// Replaces each sequence of 'tabWidth' spaces with a tab.
// Ignores smaller numbers of spaces (similar to VS2010).
if (String.IsNullOrWhiteSpace(text))
return "";
string spaces = new String(' ', tabWidth);
return text.Replace(spaces, "\t");
}
public static string Untabify(string text, int tabWidth)
{
if (String.IsNullOrWhiteSpace(text))
return "";
string spaces = new String(' ', tabWidth);
return text.Replace("\t", spaces);
}
}
}
</pre>Jim Davieshttp://www.blogger.com/profile/13198336058804859406noreply@blogger.com0tag:blogger.com,1999:blog-2652700656634857296.post-24551563906077150312011-06-30T12:36:00.001+01:002011-06-30T12:37:41.410+01:00C# - String Extensions - Various<h1>Usage</h1><br />
Various fairly self-explanatory short string extensions.<br />
<br />
<h1>Snippet</h1><br />
<pre class="brush: csharp">using System;
using System.Collections.Generic;
using System.Linq;
namespace My.Helpers
{
// String extension methods.
public static class StringExtensions
{
public static int CompareTrimmed(this string text, string str, bool ignoreCase = true)
{
return String.Compare(text.Trim(), str.Trim(), ignoreCase);
}
public static bool ContainsIgnoreCase(this string text, string value)
{
return (text.IndexOf(value, System.StringComparison.OrdinalIgnoreCase) > 0);
}
public static bool ContainsIgnoreCase(this string text, string value, out int index)
{
index = text.IndexOf(value, System.StringComparison.OrdinalIgnoreCase);
return (index >= 0);
}
public static bool EndsWithIgnoreCase(this string text, string value)
{
return text.EndsWith(value, System.StringComparison.CurrentCultureIgnoreCase);
}
public static string Left(this string text, int maxLength)
{
if (maxLength <= 0)
return "";
int count = Math.Min(maxLength, text.Length);
return text.Substring(0, count);
}
public static string ReplaceIgnoreCase(this string text, string value1, string value2)
{
string work = text;
int start = 0;
var caseInsensitive = System.StringComparison.CurrentCultureIgnoreCase;
while (true)
{
int i = work.IndexOf(value1, start, caseInsensitive);
if (i < 0) break;
work = work.Left(i) + value2 + work.Right(work.Length - i - value1.Length);
start += value2.Length;
}
return work;
}
public static string Right(this string text, int maxLength)
{
if (maxLength <= 0)
return "";
int start = Math.Max(text.Length - maxLength, 0);
int count = Math.Min(maxLength, text.Length - start);
return text.Substring(start, count);
}
public static bool StartsWithIgnoreCase(this string text, string value)
{
return text.StartsWith(value, System.StringComparison.CurrentCultureIgnoreCase);
}
public static string StripNonNumeric(this string text)
{
return text.StripNonNumeric(false);
}
public static string StripNonNumeric(this string text, bool stopAtFirst)
{
string result = "";
foreach (var c in text)
{
if ((c.CompareTo('0') >= 0) && (c.CompareTo('9') <= 0))
result += c;
else
{
if (stopAtFirst)
break;
}
}
return result;
}
public static byte[] ToByteArray(this string text)
{
int count = text.Length;
byte[] result = new byte[count];
for (int i = 0; i < count; i++)
result[i] = (byte)text[i];
return result;
}
public static string ToCsv(this string text)
{
// Converts comma-delimited or space-delimited to comma-delimited.
string[] fields;
if (text.Contains(",") || !text.Contains(" "))
return text;
// 'text' contains one or more spaces.
string result = "";
fields = text.Split(" ".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
foreach (string field in fields)
{
if (result != "")
result += ",";
result += field.Trim();
}
return result;
}
public static int[] ToIntArray(this string text)
{
return Array.ConvertAll<string, int>(text.ToCsv().ToStringArray(), int.Parse);
}
public static List<int> ToListInt(this string text)
{
return text.ToIntArray().ToList<int>();
}
public static string[] ToStringArray(this string text)
{
string[] result = text.ToCsv().Split(",".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < result.Count(); i++)
result[i] = result[i].Trim();
return result;
}
public static List<string> ToListString(this string text)
{
return text.ToStringArray().ToList<string>();
}
}
}
</pre>Jim Davieshttp://www.blogger.com/profile/13198336058804859406noreply@blogger.com0tag:blogger.com,1999:blog-2652700656634857296.post-65291705055890850152011-06-30T12:17:00.001+01:002011-06-30T12:30:13.296+01:00C# - String Extensions - AddMarkup<h1>Usage</h1><br />
string foo = "I got this from Just Snippets!";<br />
string bar = foo.AddMarkup("Just Snippets", "<b>", "</b>");<br />
<br />
would set <b>bar</b> to "I got this from <b>Just Snippets</b>!".<br />
<br />
<h1>Snippet</h1><br />
<pre class="brush: csharp">namespace My.Helpers
{
// String extension methods.
public static class StringExtensions
{
public static string AddMarkup(this string text, string value, string before, string after, bool ignoreCase = true)
{
string work = text;
int start = 0;
System.StringComparison caseSensitivity;
int bump = value.Length + before.Length + after.Length;
if (ignoreCase)
caseSensitivity = System.StringComparison.CurrentCultureIgnoreCase;
else
caseSensitivity = System.StringComparison.CurrentCulture;
while (true)
{
int i = work.IndexOf(value, start, caseSensitivity);
if (i < 0) break;
work = work.Left(i) + before + work.Substring(i, value.Length) + after
+ work.Right(work.Length - i - value.Length);
start += bump;
}
return work;
}
</pre>Jim Davieshttp://www.blogger.com/profile/13198336058804859406noreply@blogger.com0tag:blogger.com,1999:blog-2652700656634857296.post-70827325076040541262011-06-30T11:51:00.001+01:002011-06-30T12:13:33.093+01:00C# - Byte Array Extensions - ToHexString<pre class="brush: csharp">using System.Text;
namespace My.Helpers
{
public static class ByteExtensions
{
public static string ToHexString(this byte[] array)
{
// Create a new StringBuilder to collect the bytes and create a string.
var sBuilder = new StringBuilder();
// Convert to a hex string.
for (int i = 0; i < array.Length; i++)
sBuilder.Append(array[i].ToString("x2"));
// Return the hex string.
return sBuilder.ToString();
}
}
}
</pre>Jim Davieshttp://www.blogger.com/profile/13198336058804859406noreply@blogger.com0tag:blogger.com,1999:blog-2652700656634857296.post-20043025587760936822011-06-30T11:46:00.000+01:002011-06-30T11:46:19.015+01:00ASP.NET MVC - Admin HelpersA small helper to add or remove a role from an ASP.NET user.<br />
<br />
<pre class="brush: csharp">using System.Web.Security;
namespace My.Helpers
{
public class AdminHelpers
{
public static bool SetRole(string userName, string roleName, bool newState)
{
bool currState = Roles.IsUserInRole(userName, roleName);
if (newState != currState)
{
if (newState)
{
// Add the 'roleName' role to this user.
Roles.AddUserToRole(userName, roleName);
}
else
{
// Remove the 'roleName' role from this user.
Roles.RemoveUserFromRole(userName, roleName);
}
}
return true;
}
}
}
</pre>Jim Davieshttp://www.blogger.com/profile/13198336058804859406noreply@blogger.com0tag:blogger.com,1999:blog-2652700656634857296.post-21681324123639059702011-06-30T11:34:00.002+01:002011-07-20T08:49:38.001+01:00ASP.NET MVC Mini-Profiler - Linq to SQLThis is a useful free <a href="http://code.google.com/p/mvc-mini-profiler/">profiler for ASP.NET MVC</a> from the people at StackOverflow.com.<br />
<br />
Some advice re. usage is in my <a
href="http://justsnippets.blogspot.com/2011/06/aspnet-mvc-mini-profiler-first.html">previous post</a>.<br />
<br />
This post shows one way to use it in ASP.NET MVC with Linq to SQL.<br />
<br />
<br />
<h1>In A DbRepository</h1><br />
I'm assuming a repository-based approach similar to NerdDinner.<br />
<br />
I usually let my repository classes share a 'DataContext' - each controller typically has a main repository which creates the data context as usual, but other repositories needed in the controller actions are created using the second constructor below, and therefore use the first repository's data context.<br />
<br />
<pre class="brush: csharp"> public class MyDbRepository : IMyRepository
{
public MyDataContext DataContext { get; protected set; }
// Constructors.
public MyDbRepository()
{
MiniProfiler profiler = MiniProfiler.Current;
// Grab the SQL connection string (or could get it from web.config, for example).
DataContext = new MyDataContext();
string connString = DataContext.Connection.ConnectionString;
// Setup a profiled connection.
var conn = new SqlConnection(connString);
var profiledConn = MvcMiniProfiler.Data.ProfiledDbConnection.Get(conn, profiler);
// Create another data context using the profiled connection.
DataContext = new MyDataContext(profiledConn);
}
public MyDbRepository(IMyRepository repos)
{
// Repositories can share a data context object.
DataContext = repos.DataContext;
}
// Public methods.
// +++
}
</pre>Jim Davieshttp://www.blogger.com/profile/13198336058804859406noreply@blogger.com2tag:blogger.com,1999:blog-2652700656634857296.post-65092432416950371422011-06-29T09:54:00.020+01:002011-07-20T08:45:50.454+01:00ASP.NET MVC Mini-Profiler - First ImpressionsThis is a useful free <a href="http://code.google.com/p/mvc-mini-profiler/">profiler for ASP.NET MVC</a> from the people at StackOverflow.com (a wonderful source of answers, and built using ASP.NET MVC).<br />
<br />
The easiest way to install it in VS2010 is using <a href="http://nuget.org/List/Packages/MiniProfiler">NuGet</a><br />
<br />
These snippets show one way to use it.<br />
<br />
<span class="file">Site.Master (.ASPX view engine)</span><br />
<br />
<pre class="brush: js"><script src="<%: Url.Content("https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js") %>" type="text/javascript"></script>
<%= MvcMiniProfiler.MiniProfiler.RenderIncludes() %>
</pre><br />
<span class="file">Global.asax.cs</span><br />
<br />
A simple approach is to start and stop the profiler for each separate web request, for example:<br />
<br />
<pre class="brush: csharp">using MvcMiniProfiler;
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_BeginRequest()
{
if (Request.IsLocal)
MiniProfiler.Start();
}
protected void Application_EndRequest()
{
if (Request.IsLocal)
MiniProfiler.Stop();
}
}
</pre><br />
<h1>In A Controller, for example</h1><br />
Create a separate MiniProfiler 'step' for any long processing steps that you want to profile:<br />
<br />
<pre class="brush: csharp">var profiler = MiniProfiler.Current;
// No need to check 'profiler' for null here, it's handled in profiler.Step()
using (profiler.Step("Search"))
{
// Time-consuming stuff...
DoLongSearch(xxx);
}
</pre><br />
Steps can be nested, and the profile output will show the step structure:<br />
<br />
<pre class="brush: csharp"> using (profiler.Step("SearchAndUpdate"))
{
using (profiler.Step("Search"))
{
// Time-consuming stuff...
DoSearch(xxx);
}
using (profiler.Step("Update"))
{
// Time-consuming stuff...
DoUpdate(xxx);
}
}
</pre><br />
<h1>Viewing the Output</h1><br />
When you navigate to a URL that causes the profiler to be called, the resulting view will contain a small MiniProfiler gadget at the top left. Click on it to get the timing detail.<br />
<br />
<h1>Linq to SQL</h1><br />
You can use the MiniProfiler to profile database calls, including Linq to SQL - see <a href="http://justsnippets.blogspot.com/2011/06/aspnet-mvc-mini-profiler-linq-to-sql.html">this post</a>. The profile information includes the elapsed time of each query and also warns of 'duplicate' SQL queries, both of which can help in optimizing application design.Jim Davieshttp://www.blogger.com/profile/13198336058804859406noreply@blogger.com2