Search This Blog

Saturday, December 26, 2009

Injecting Javasript into ASP.NET/MVC with a litlle help from ? Rich Stahl

An example of using JQuery with ASP.NET/MVC with Rick Stahl''s Web Utility.

Let's assume you want to use JQuery where you wish to have client side data sent back to a controller without a full page post back.

For the project:
You need to inject a client-side JavaScript. While you can spin one up by hand, why not use go green and recycle? Rich Stahl's site: http://www.west-wind.com has a variety of tools, some of which are free. Download http://www.west-wind.com/westwindwebtoolkit/. What we're interested in here is the Web Utility Support Classes

Download the utility and add references for Westwind.Utilities and Westwind.Web to your project. You'll also need to add ScriptVariableInjectionHelper.cs to your helpers folder.

ScriptVariableInjectionHelper provides an easy way for server code to publish strings into client script code. This object basically provides a mechanism for adding key value pairs and embedding those values into an object that is hosted on the client.


For the Master Page:
Before you start injecting scripts willy-nilly all over your pages, it is better to inject all of your client-side scripts into the header along with your other JavaScript includes.. If you are using a master page, this becomes very easy. Beneath where you put all of your other site-wide include tags, and above a content placeholder for page-level scripts, I also add a simple test for controller scripts I inject into the ViewData and a test to verify it is there before you write it out.

<head>
<script type="text/javascript" language="javascript" src="<%=Url.Content(">"></script>
<% if(ViewData.Contains("clientScript")){
Response.Write(ViewData["clientScript"].ToString());
}%>
asp:contentplaceholder id="Scripts" runat="server">
</head>

For the Controller:
Now that we have a way to inject our script and a target to inject it into, we need to simply send it down for manipulation.


From your ActionMethod we add a call to a new InjectClientScript method...
public virtual ActionResult Edit(Guid id)
{

Criterion model = _CriterionSvc.GetCriterion(id);
InjectClientScript(model);
return View(model);
}

private void InjectClientScript(object model)
{
// instantiate the utility (view is the name of the JavaScript object that will appear in the View
ScriptVariables scriptVars = new ScriptVariables("view");
// add our ProfileCommon data (user context)
scriptVars.Add("Profile", profile.Preferences);
// add our model being injected
if (model != null)
scriptVars.Add("Model", model);
// call the westwind utility and inject into viewdata
ViewData["clientScript"] = scriptVars.GetClientScript(true);
}

For the view:
The output is injected into a variable called view, which in turn contains our Controller objects we injected (Profile and Model). Note Model contains objects of its own. In this example, CriterionType is one such object. You can configure the westwind utility to traverse child objects or not.



You now simply need to access your javascript variables.Below is a simple example of accessing those variables and sending them to a jquery function that would populate a dropdown.

$.PopulateDropdown("CriterionTypeId", "/CriterionType/GetCriterionTypeCollection/" + view.Model.CriterionType.DomainId, view.Model.CriterionTypeId);

Wednesday, October 8, 2008

Reoccuring Scheduling Logic (continued)

When designing a scheduling system, if your business rules allow you to have a one to many relationship between scheduled times and items being scheduled you are going to need to know which record is the one that should determine if the record should play or not. For example, a pattern may not fit neatly into a one week range (i.e. Mon, Wed, Fri) and your pattern may require wrapping your schedulE across weeks with something running from Wed-Sun. Remember from our database functions (or consulting a the calendar on the wall) that weeks begin on Sunday, so a schedule running from Wed - Sun would require week A to run from Wed-Sat and week B to run from Sunday AM to PM. Now you have two valid date ranges concerning one file to schedule and you need to determine whether to play or suspend the schedule.

The use cases are as follows:
Schedule A is valid, Schedule B is not (so we play)
Schedule B is valid, Schedule A is not (so we play)
Schedule A and B are both not valid (so we suspend play)

We can either get all of the affected records, stuff them into a cursor and turn off the records that need switching off first, then turn on all the ones that should be active. That is fairly straightforward, procedural programming and if you spend most of your time coding in C# or VB, as opposed to in the database, then you'll approach this from a coders point of view: build an array and cycle through it. You might, if you're a little more advanced consider logic to facilitate some sort of truth table and test for the true condition. However, remember we are working in a database, so the simplest solution would be to convert the bit returned from the scheduling function and sum the results, grouped by the items being scheduled.


SELECT col1, col2, col3
sum(cast(dbo.fn_Check_Schedule(StartDate, EndDate, DaysOfWeek, getDate(), WeeklyCycle ) as int) )sched
FROM t_ItemsBeingScheduled
group by col1, col2, col3

Now you'll get one record per item being scheduled.

Saturday, September 27, 2008

Reoccuring Scheduling Logic

Ok,
So let's assume that you are a software developer and have a database driven scheduling application. Since this is a database application that is to deliver millions of requests per day, and the exact start time isn't any more granular than the nearest hour, you don't want to perform these lookups in real time. Instead you decide to create a stored procedure that will run as a scheduled job. The plan is that a scheduled job will run a stored procedure that will toggle the availability of requested files or not depending upon if the request occurs during a valid date range. Records that are available will have a bit flag set to 1. Records that aren't will have a bit flag set to 0.
Testing that you're in a valid date range is certainly easy enough and if you are writing stored procedures at all, you should already know how to do this, but for any newbee reading this, the psudo-code for such a test will be Update Schedule where now is between the start date and end date. Ok, so what if there is a requirement for scheduling things between a date range and the client only wants to deliver this information every other week or less? You could write a routine that would inject multiple date ranges into the table. So instead of a date range that spanned a 90 day period, you could inject 12 one week date ranges. That is far from elegant. Perhaps we could come up with a simple algorithym to calculate if we are within a valid week within that valid date range. We can do so by bringing a little of our high school algebra out of the closet.
You remember Algebra, don't you? It was that stuff you swore you'd never find useful in the real world. Well, here's a real world example. For this example we're going to need three data points and three variables representing them. Snce our scheduler is concerned with weekly cycles, and integers are far easier to work with, we will be converting our dates into integers using built in database DateTime functions and our three data points will be our the week of our scheduled start date (SW); an integer representing our weekly cycle (WC) which is the number of weeks that elapse before our cycle resets, and the current time (CW).
What we need to determine is what is the number of weeks between the current time and our scheduled start date, and if that number of weeks works out to an even number. We can accomplish this two ways. The first is a little more verbose, but I like it because it is more self explanitory. That is, each step in the equation provides a piece to why this formula makes sense.
((CW-SW)/WC)*WC+SW=CW
(CW-SW) = the number of weeks from the Start Week and the Current week.
((CW-SW)/WC) = the number of weekly cycles that have elapsed.
*WC = The number of weeks in the cycle itself
((CW-SW)/WC)*WC+SW= when we compute this if the result equals Current week, we set our value to true.

Let's look at a simple example where our schedule started on Sept 7th, and runs every other week and the current day is Sept 27th. Converted into integers we have
CW= 39, SW=37, WC = 2

plugged into our formula ((CW-SW)/WC)*WC+SW=CW gives us
((39-37)/2)*2+37=39

simplifies to ((2)/2)*2+37=39
simplifies to (1)*2+37=39
simplifies to 22+37=39
simplifies to 39=39.
Since the left and right side are equal we set our record to active.

Now while the above is a workable and self evident formula, let's see how else this can be done.
Remembering our algebra again if instead of testing if our equation equals CW, we can modify our equation by subtracting CW from each side to see if the result equals 0. So
((CW-SW)/WC)*WC+SW=CW
is equivelant to
((CW-SW)/WC)*WC+SW-CW=CW-CW
is equivelant to
((CW-SW)/WC)*WC+SW-CW=0

if you have SQL Server Query Analyzer open paste in the following

Declare @CW int
Declare @SW int
Declare @WC int
Select @CW = datePart(week, Getdate()), @SW= datePart(week,'9/7/2008'), @WC = 3
if ((@CW-@SW)/@WC)*@WC+@SW-@CW = 0
Print 'Perform update: Number of weeks divides evenly into the weekly cycle!'
else
print 'Can''t perform update! Number of weeks doesn''t divide evenly into the weekly cycle!'
print 'The current week is ' + cast(@CW as nvarchar(2))
print 'The start week is ' + cast(@SW as nvarchar(2))

Now we have a working formula that while not as terse as possible get's the job done and vidicates our high school math teacher. However, if you look at the formula, you might realize there is a simple math function that can replace 4 steps in the calculation. The hint is in the print statements: 'divides evenly into the weekly cycle.'
Modulus can take the place of the portion of this formula in red:
((CW-SW)/WC)*WC+SW-CW=0
as in (CW-SW)%WC= 0.

So paste the code below into Query Analyzer:

Declare @CW int
Declare @SW int
Declare @WC int
Select @CW = datePart(week, Getdate()), @SW= datePart(week,'9/7/2008'), @WC = 1
if ((@CW-@SW)%@WC) = 0
Print 'Perform update: Number of weeks divides evenly into the weekly cycle!'
else
print 'Can''t perform update! Number of weeks doesn''t divide evenly into the weekly cycle!'
print 'The current week is ' + cast(@CW as nvarchar(2))
print 'The start week is ' + cast(@SW as nvarchar(2))

So there you have it. You can now calculate schedules that occur in frequencies less than every week. Of course, the same logic could be used to calculate monthly or other cycles.

Here is the entire thing in a function:


ALTER FUNCTION [dbo].[fn_Check_Schedule]
(@StartDate DATETIME,
@EndDate DATETIME,
@DaysOfWeek TINYINT,
@CurrDate DATETIME,
@WeeklyCycle int
)
RETURNS BIT AS
BEGIN
DECLARE @returnval bit
Declare @ValidWeek int
set @returnval = 0
Set @ValidWeek = 0
if @WeeklyCycle > 1
BEGIN
Select @ValidWeek = ((Datepart(week, @CurrDate) - Datepart(week, @StartDate))%@WeeklyCycle)
-- Mod % performs the same function as the broken out logic below in notes
END
if @ValidWeek = 0
BEGIN
-- If the schedule isn't set to play on every day of the week...
IF (@DaysOfWeek & 128 != 128)
BEGIN
-- We're going to check to make sure that today is an applicable day to play the presentation
-- If so, we'll move on to check into the date/time intervals. If not, we'll return false.
DECLARE @DayOfWeek NVARCHAR(10)
SELECT @DayOfWeek = DATENAME(dw, @CurrDate)
IF (@DayOfWeek = 'Sunday' AND (@DaysOfWeek & 64 != 64))
RETURN 0
IF (@DayOfWeek = 'Monday' AND (@DaysOfWeek & 32 != 32))
RETURN 0
IF (@DayOfWeek = 'Tuesday' AND (@DaysOfWeek & 16 != 16))
RETURN 0
IF (@DayOfWeek = 'Wednesday' AND (@DaysOfWeek & 8 != 8))
RETURN 0
IF (@DayOfWeek = 'Thursday' AND (@DaysOfWeek & 4 != 4))
RETURN 0
IF (@DayOfWeek = 'Friday' AND (@DaysOfWeek & 2 != 2))
RETURN 0
IF (@DayOfWeek = 'Saturday' AND (@DaysOfWeek & 1 != 1))
RETURN 0
END
-- No need to split date-only part because before the start time on the start date would still be the same result, but the conversion would hurt performance.
select @returnval = case when @CurrDate between @StartDate and @EndDate AND convert(varchar,@CurrDate,108) between convert(varchar,@StartDate,108) AND convert(varchar,@EndDate,108) then 1
else 0 end
END
return @returnval



END