On the Linkedin Control-M group there have been a couple of questions on how to trigger a job, or raise an error, once a cyclic job had failed n number of times. I'm not too keen on such a pattern - if a job fails once it should stay failed until the root cause is fixed. If an error is to be expected, then the process called by the job should gracefully handle that case, and fail for all others. However, if you have a valid case to do this, then here is a short guide on implementing same. This works on Control-M version 7.
1) Create the cyclic job that could fail. In this job, create an ON statement where the CODE=NOTOK, DO <condition> (to be used by the second job below), and DO rerun.
2) Create a job that will catch the error. This is another cyclic job that is triggered by the condition posted from job 1's ON statement and will also delete this condition on completion (so it will only execute when job 1 runs, fails and posts the condition). For this job, put the following command line
echo ERROR_RUN_%%RUNCOUNT.
(Should work for Windows and Unix hosts) On the Steps tab of this job, create an ON statement where CODE=*ERROR_RUN_3* DO NOTOK,
What will happen here is that when this job is run 3 times, it will fail due to the DO NOTOK (change the 3 for however many cycle you need) You can then use this job to raise an Shout or trigger of some other process you require.
3) To extend this solution, by making it possible to re-run job 2, you will need to change the command line in job 2 so that you can pick up the error on every n'th run. One way to do this is to alter the command to print out the run count divided by the number of runs to fail on. In many programming languages this is the result of the moduls operator. For example, if you are running on a windows host you could use the following as job 2 command line:
powershell -command "$result=%1 %% 3; $themsg='ERROR_RUN_' + $result; Write-Host $themsg"
(you can do something similar in UNIX as many shells have a built in modulus operator, or you could run a AWK script to print out the result). Add on the steps tab an entry PARM1=%%RUNCOUNT, which becomes the run count (%1) on the command line at run-time. The Powershell modulus operator % needs to be escaped by another % which is why the command contains %% due to the way that Control-M parses windows command lines.
Then alter the ON statement to DO NOTOK (or what ever other action to take) when CODE=ERROR_RUN_0 (because you want to error on every 3rd run, as the result of 3 modulus 3 is 0) and now you have a job that will fail on every 3rd run.
Saturday, 18 June 2016
Monday, 6 June 2016
A worked example of autocomplete for a ASP.NET MVC application using HTML
In my previous post I noted that a autocomplete could be implemented using a HTML <datalist>, so here is an example of implementing this. The approach I have taken is to create a partial view that contains the search text field and the <datalist>. There is a corresponding controller action that is called by the page when it is loaded. I left the code to create the form and the submit button in the parent page basically so I could toggle between this solution and the jQuery version.
So, the partial view looks like
@model List<string>
<input list="searchString" name="searchString">
<datalist id="searchString">
@{
foreach (string mName in Model)
{
<option value = "@mName" ></option>
}
}
</datalist>
The Model used by this partial view is defined in the controller.
[ChildActionOnly]
public ActionResult listMemberNames( )
{
List<Member> memberList = db.Members.ToList();
List<string> mbrNames = new List<string>();
foreach(Member mbr in memberList)
{
mbrNames.Add(mbr.MemberName);
}
mbrNames.sort();
return PartialView("_memberNamesList", mbrNames);
}
The performance of this method could be improved by querying just the unique member names from the database rather than using entity framework to retrieve all the member information (db.Members.ToList()),then iterating this list to get the unique list of names which is then sorted. In this case member name is made up of three columns, memberSalutation + memberFirstName + memberLastName so I have not bothered to do that optimization for this example.
Finally, in the parent view:
@using (Html.BeginForm("Index", "Members", FormMethod.Get))
{
<p>
@*Find by name: @Html.TextBox("searchString", ViewBag.CurrentFilter as String, new { data_autocomplete_source = Url.Action("QuickSearchMembers", "Members") })*@
@Html.Action("listMemberNames")
<input type="submit" value="Search" />
</p>
<p>
@Html.ActionLink("Create New", "Create")
</p>
}
Run the app, and the rendered HTML for this page contains the markup we want:
So, the partial view looks like
@model List<string>
<input list="searchString" name="searchString">
<datalist id="searchString">
@{
foreach (string mName in Model)
{
<option value = "@mName" ></option>
}
}
</datalist>
The Model used by this partial view is defined in the controller.
[ChildActionOnly]
public ActionResult listMemberNames( )
{
List<Member> memberList = db.Members.ToList();
List<string> mbrNames = new List<string>();
foreach(Member mbr in memberList)
{
mbrNames.Add(mbr.MemberName);
}
mbrNames.sort();
return PartialView("_memberNamesList", mbrNames);
}
The performance of this method could be improved by querying just the unique member names from the database rather than using entity framework to retrieve all the member information (db.Members.ToList()),then iterating this list to get the unique list of names which is then sorted. In this case member name is made up of three columns, memberSalutation + memberFirstName + memberLastName so I have not bothered to do that optimization for this example.
Finally, in the parent view:
@using (Html.BeginForm("Index", "Members", FormMethod.Get))
{
<p>
@*Find by name: @Html.TextBox("searchString", ViewBag.CurrentFilter as String, new { data_autocomplete_source = Url.Action("QuickSearchMembers", "Members") })*@
@Html.Action("listMemberNames")
<input type="submit" value="Search" />
</p>
<p>
@Html.ActionLink("Create New", "Create")
</p>
}
Run the app, and the rendered HTML for this page contains the markup we want:
<h2>Members</h2>
<form action="/Members" method="get"> <p>
<input list="searchString" name="searchString">
<datalist id="searchString">
<option value = " The Presedant" ></option>
<option value = "Mrs Jane Doe" ></option>
<option value = "Mr John Doe" ></option>
<option value = "Mrs Member1 Mbr1" ></option>
<option value = "Mrs Member2 Mbr2" ></option>
<option value = "Mrs Member3 Mbr3" ></option>
<option value = "Mrs Member4 Mbr4" ></option>
<option value = "Mrs Member5 Mbr5" ></option>
<option value = "Mrs Member6 Mbr6" ></option>
<option value = "Mrs Member7 Mbr7" ></option>
<option value = "Mrs Member8 Mbr8" ></option>
<option value = "Mrs Member9 Mbr9" ></option>
</datalist>
<input type="submit" value="Search" />
</p>
<p>
Thursday, 2 June 2016
A worked example of autocomplete for a ASP.NET MVC application using jQuery
This post contains a worked example of a autocomplete text box for a ASP.NET MVC application along with some comments on the implementation.
The first step is to add the jQuery library for jQuery.UI.Combined by adding it to the project using Nuget
Once that has been added, ensure that the scripts are served up by adding this code to RegisterBundles(BundleCollection bundles) in \App_Start\BundleConfig.cs
bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include("~/Scripts/jquery-ui-{version}.js"));
With the library included in the build, add a include for autocomplete.css and jqueryui into the .cshtml file where it is required. In my project I added it to _Layout.cshtml
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - Toy Library</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryui")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
<link href="~/Content/themes/base/jquery-ui.css" rel="stylesheet" type="text/css" />
<link href="~/Content/themes/base/autocomplete.css" rel="stylesheet" type="text/css" />
</head>
Next is to add a method to the controller that will return a Json list of the search terms you need. This example is a little longer that what you would normally need because the search string is made up of two properties of the model - ie the code below to gets two lists then concatenate them together to create the final list.
//return a list of member names which contain the searchExpr
//for use by the autocomplete search box for members data_autocomplete_source = Url.Action("QuickSearchMembers", "Members")
public ActionResult QuickSearchMembers( string term )
{
List<Member> memberList = db.Members.Where(a => a.FirstName.Contains(term)).ToList();
List<Member> memberListLast = db.Members.Where(b => b.LastName.Contains(term)).ToList();
//merge together.
if (memberList.Count > 0) { memberList.Concat(memberListLast); }
else { memberList = (List<Member>)memberListLast; }
//return only 5 items to reduce bandwidth
if (memberList.Count > 5)
{
memberList.RemoveRange(5, memberList.Count - 5);
}
var memberNames = memberList.Select(b => new { value = b.MemberName });
return Json(memberNames, JsonRequestBehavior.AllowGet);
}
Another feature of this code is that the returned list is limited to 5 values. This is done to reduce the size of the Json packet sent on each request if there is the potential to have a large amount of records retruned (I have some more comments on this below).
In the .cshtml page where this will be used, add the following mark-up to display the search text box
@using (Html.BeginForm("Index", "Members", FormMethod.Get))
{
<p>
Find by name: @Html.TextBox("searchString", ViewBag.CurrentFilter as String, new { data_autocomplete_source = Url.Action("QuickSearchMembers", "Members")})
<input type="submit" value="Search" />
</p>
}
Note the name of the first argument to Url.Action - this needs to match the name of the method in the jQuery function that is also added to the page:
<!-- below is to autocomplete search text box -->
<script type="text/javascript" language="javascript">
$(document).ready(function () {
$("#searchString").autocomplete({
source: '@Url.Action("QuickSearchMembers", "Members")'
});
})
</script>
To change the styling of the suggested values, you can edit the .css file /Content/themes/base/autocomplete.css
.ui-autocomplete {
position: absolute;
top: 0;
left: 0;
cursor: default;
background-color: lightgray;
}
For example, I added an attribute to make the background light grey so the suggestions are easier to read
So, how does this work? When the user changes the content of the search text box the .autocomplete jQuery method is called, which will then call the public ActionResult QuickSearchMembers( string term ) method from the controller returning a list of suggestions that is displayed on the screen. Due to this, this technique can result in a lot of network traffic as each character entered (or removed) from the text box will result in a call to the server and the response of a Json payload - which is why I think it is a good idea to limit the number of items returned in the Json packet.
An alternative method is to make use of the HTML <datalist> tag and a list of data returned from the controller. see http://www.w3schools.com/tags/tag_datalist.asp for some additional details. This method would reduce network requests to once per page request. However, this would only work if the number of possible suggestions is small otherwise page size would become large and page load times would suffer. (edit - see my next post on the implementation of this method)
One drawback to both methods is that additional data is server up from the web server, which mean additional load and for a cloud hosted application additional cost. So for a high traffic site, you would need to ask if the autocomplete feature is really worth the additional expense.
The first step is to add the jQuery library for jQuery.UI.Combined by adding it to the project using Nuget
Once that has been added, ensure that the scripts are served up by adding this code to RegisterBundles(BundleCollection bundles) in \App_Start\BundleConfig.cs
bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include("~/Scripts/jquery-ui-{version}.js"));
With the library included in the build, add a include for autocomplete.css and jqueryui into the .cshtml file where it is required. In my project I added it to _Layout.cshtml
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewBag.Title - Toy Library</title>
@Styles.Render("~/Content/css")
@Scripts.Render("~/bundles/modernizr")
@Scripts.Render("~/bundles/jquery")
@Scripts.Render("~/bundles/jqueryui")
@Scripts.Render("~/bundles/bootstrap")
@RenderSection("scripts", required: false)
<link href="~/Content/themes/base/jquery-ui.css" rel="stylesheet" type="text/css" />
<link href="~/Content/themes/base/autocomplete.css" rel="stylesheet" type="text/css" />
</head>
Next is to add a method to the controller that will return a Json list of the search terms you need. This example is a little longer that what you would normally need because the search string is made up of two properties of the model - ie the code below to gets two lists then concatenate them together to create the final list.
//return a list of member names which contain the searchExpr
//for use by the autocomplete search box for members data_autocomplete_source = Url.Action("QuickSearchMembers", "Members")
public ActionResult QuickSearchMembers( string term )
{
List<Member> memberList = db.Members.Where(a => a.FirstName.Contains(term)).ToList();
List<Member> memberListLast = db.Members.Where(b => b.LastName.Contains(term)).ToList();
//merge together.
if (memberList.Count > 0) { memberList.Concat(memberListLast); }
else { memberList = (List<Member>)memberListLast; }
//return only 5 items to reduce bandwidth
if (memberList.Count > 5)
{
memberList.RemoveRange(5, memberList.Count - 5);
}
var memberNames = memberList.Select(b => new { value = b.MemberName });
return Json(memberNames, JsonRequestBehavior.AllowGet);
}
Another feature of this code is that the returned list is limited to 5 values. This is done to reduce the size of the Json packet sent on each request if there is the potential to have a large amount of records retruned (I have some more comments on this below).
In the .cshtml page where this will be used, add the following mark-up to display the search text box
@using (Html.BeginForm("Index", "Members", FormMethod.Get))
{
<p>
Find by name: @Html.TextBox("searchString", ViewBag.CurrentFilter as String, new { data_autocomplete_source = Url.Action("QuickSearchMembers", "Members")})
<input type="submit" value="Search" />
</p>
}
Note the name of the first argument to Url.Action - this needs to match the name of the method in the jQuery function that is also added to the page:
<!-- below is to autocomplete search text box -->
<script type="text/javascript" language="javascript">
$(document).ready(function () {
$("#searchString").autocomplete({
source: '@Url.Action("QuickSearchMembers", "Members")'
});
})
</script>
To change the styling of the suggested values, you can edit the .css file /Content/themes/base/autocomplete.css
.ui-autocomplete {
position: absolute;
top: 0;
left: 0;
cursor: default;
background-color: lightgray;
}
For example, I added an attribute to make the background light grey so the suggestions are easier to read
So, how does this work? When the user changes the content of the search text box the .autocomplete jQuery method is called, which will then call the public ActionResult QuickSearchMembers( string term ) method from the controller returning a list of suggestions that is displayed on the screen. Due to this, this technique can result in a lot of network traffic as each character entered (or removed) from the text box will result in a call to the server and the response of a Json payload - which is why I think it is a good idea to limit the number of items returned in the Json packet.
An alternative method is to make use of the HTML <datalist> tag and a list of data returned from the controller. see http://www.w3schools.com/tags/tag_datalist.asp for some additional details. This method would reduce network requests to once per page request. However, this would only work if the number of possible suggestions is small otherwise page size would become large and page load times would suffer. (edit - see my next post on the implementation of this method)
One drawback to both methods is that additional data is server up from the web server, which mean additional load and for a cloud hosted application additional cost. So for a high traffic site, you would need to ask if the autocomplete feature is really worth the additional expense.
Subscribe to:
Posts (Atom)