Wednesday, December 22, 2010

Ext.ux.JSONP v2.0

The ExtJS library does include a JSONP component, but I found it to be lacking a very important piece of functionality.

The Problem: No Error Handler

Anyone who know's anything about JSONP is immediately going to call attention to the fact that JSONP isn't capable of supporting error handling by design. While that is true, it doesn't mean that we don't have to handle errors. One of the more well known jQuery plugins for JSONP includes a callback method for "complete" which enables you to know when a request failed.

One additional nitpick that I had with the ExtJS JSONP component what that I did not feel it was coded in a very Object Orient fashion, but that's probably just a personal gripe.

The Solution: v2.0

I rewrote the Ext.ux.JSONP component to include additional parameters that match up with a normal Ext.Ajax.request. This means that you can access handlers for success, failure, and callback.

Also, I made the code much more OO.


Ext.ux.JSONP v2.0


// Old
Ext.ux.JSONP.request('', {
   callbackKey: 'jsoncallback',
   params: {
       format: 'json',
       tagmode: 'all',
       lang: 'en-us'                            
   callback: updateResults

// New
   url : '',
   params: {
       format: 'json',
       tagmode: 'all',
       lang: 'en-us'                            
   success : this.updateSuccess,
   failure : this.updateFailure,
   callback : this.updateCallback,
   scope : this

Thursday, November 11, 2010

Migration in Progress

Welcome to my new blog, TomDuPont.NET

I am currently in the process of migrating my old blog to this site. I have been actively blogging since 2008, so getting all of those entries moved over is a little daunting, but I am getting there!
For now, assuming you were looking for content, please visit my CodeSmith Tools Blog.

Wednesday, October 13, 2010

Insight Launch Party a Huge Success

The CodeSmith Insight Launch Party was a huge success. Thanks to the almost one hundred people that came out and partied with us. It was a blast, thank you all so much for coming out!

We did a 15 minute demo of Insight before we gave out prizes. To show that we had nothing up our sleeves the demo was completely unrehearsed and using our live production servers. I am pleased to say that it went off without a hitch! We even had some surprise audience participation as people in the crowd sent in emails to our demo instance during the presentation; not only was that fun, but it also really helped demonstrate the power and versatility of Insight in real time.

At the end of the demo we actually went overtime doing Q&A about Insight. Not only did we get asked a ton of questions from all around the room, but honestly they were great questions! This was flattering for two reasons: 1) As the speaker those questions told me that the audience was paying attention, and that what I was saying was intelligible. 2) As a developer it was great to see other developers thinking about use cases for Insight, and then being able to be one step ahead of the game and say "Yes, we support that."

Here are a few of the questions we were asked about Insight:

  • Can Insight report handled exceptions?
    • Yes. Insight's client API allows you to create cases for anything at anytime, and provides a series of CreateCase method overrides to make it especially easy to create a report from a caught exception.
  • Can Insight be used to submit feedback without reporting errors?
    • Yes. Insight can be configured to not report unhandled exceptions, and then you can still use the feedback forms and API to create and submit cases based only on user feedback.
  • Does Insight integrate with Microsoft Exchange?
    • Yes. Insight can send and receive email from almost any POP3 or IMAP server, that includes Microsoft Exchange.
  • Do you offer an installed solution?
    • This is the only question to which I had to answer "no." However, we are very interested in offering installed solutions in the future, so if you are interested in this please contact us directly (at and we will be more than happy to try and work with you!

In summary, the party was a blast. I think everyone had fun; I know I did. Insight is now released and available to log exceptions for developers everywhere. Also, feel free to check out the party photos we posted up on Facebook. (If you are in one and want to be tagged just let us know!)

Thanks again,
Tom DuPont

On behalf of,
The CodeSmith Tools

Friday, October 8, 2010

MVC2 Unit Testing, Populating ModelState

I love how testable ASP.NET MVC is, I also love MVC2's model validation. However when trying to unit test a controller method that used the ModelState, I quickly learned that the ModelState is not populated when just newing up a Controller and calling one of its public methods. As usual, I think this is best narrated by example:

Example Model and Controller

public class PersonModel
  public string Name { get; set; }

public class PersonController : Controller
  public ViewResult Register(Person person)
    return View(new PersonModel()); 

  public ViewResult Register(Person person) 
    if (!ModelState.IsValid) 
      return View(model); 

    return View("success");

Example of the Problem

public void RegisterTest() 
  var model = new PersonModel { Name = String.Empty }; // This is model is invalid.
  var controller = new PersonController(); 
  var result = controller.Register(model);

  // This fails because the ModelState was valid, although the passed in model was not. 
  Assert.AreNotEqual("success", result.ViewName);


Other solutions I have come across were adding the errors to the model state manually, or mocking the ControllerContext as to enable the Controller's private ValidateModel method. I didn't like the former because it felt like I wasn't actually testing the model validation, and I didn't like the latter because it seemed like a lot of work to both mocking things and then still have to manually expose a private method.

My solution is (I feel) pretty simple: Add an extension method to the ModelStateDictionary that allows you to pass in a model, and it will then validate that model and add it's errors to the dictionary.

public static void AddValidationErrors(this ModelStateDictionary modelState, object model)
  var context = new ValidationContext(model, null, null); 
  var results = new List<ValidationResult>(); 
  Validator.TryValidateObject(model, context, results, true);

  foreach (var result in results) 
    var name = result.MemberNames.First(); 
    modelState.AddModelError(name, result.ErrorMessage);

Example of the Solution

public void RegisterTest() 
  var model = new PersonModel { Name = String.Empty }; // This is model is invalid.
  var controller = new PersonController(); 

  // This populates the ModelState errors, causing the Register method to fail and the unit test to pass.
  var result = controller.Register(model);
  Assert.AreNotEqual("success", result.ViewName); 

Thursday, September 30, 2010

Oct 12th is (Insight) Party Time!

We are very excited about our upcoming CodeSmith Insight Launch Party. We rented out the Addison Convention Center, ordered ten old school arcade machines, bought over $1,000 in prizes, booked flights for the entire CodeSmith Tools team is come in from around the country, and now we are counting down until party time!

However, there have been some questions (and even concerns) about party which I feel need to be addressed.

First: This party is NOT a time-share-condo-ploy.

We are going to speak about Insight for 10 minutes, and field questions for another 5 minutes. This is a three hour party, we are only going to do "business" for 15 minutes total. That is all.

Why? It's a PARTY. We have been working our butts off for almost two years developing Insight, now we want to CELEBRATE (good times, come on)! Seriously, we are renting arcade games because they are FUN.

Second: What does "over $1,000 in prizes" mean?

Well first of all, that number does not even include:

  • Free T-Shirts
  • Free Pizza Hut Pizza
  • 50% Lifetime Discount for all Attendees

The $1,000 in prizes is made up of the following:

  • Apple IPad
  • Crucial RealSSD C300 128GB
  • Microsoft X-Box 360
  • Nintendo Wii

...and there will be additional door prizes! THAT is what "over $1,000 in prizes" means.


Please feel free to invite your coworkers, your friends, even your mother. (Seriously, my Mom will be there!)

Tuesday, October 12th
6:00pm - 9:00pm

Addison Conference Center‎
15650 Addison Road
Addison, TX‎ ‎75001

RSVP, and I will see you there!

Tom DuPont
Vice President, CodeSmith Tools 

Friday, July 9, 2010

How To: Customize Unhandled Exception Reports in CodeSmith Insight

One of the top benefits of integrating CodeSmith Insight into your application is that it will automatically catch and report all unhandled exceptions. As quick and easy as it is to just let Insight do all the work, you may want to make customizations to these unhandled exception reports. This is very easy to do: Just register for the UnhandledExceptionReporting event in your application startup, and then you can use an event handler to programmatically customize your reports.


protected void Application_Start(object sender, EventArgs e)
    InsightManager.Current.UnhandledExceptionReporting += OnUnhandledExceptionReporting;

void OnUnhandledExceptionReporting(object sender, UnhandledExceptionReportingEventArgs e)
    // You can get access to the report here...
    var report = e.CaseReport;

    // ...and make any updates you wish!

Friday, June 18, 2010

Video: Mailboxes in CodeSmith Insight

The primary goal of CodeSmith Insight is to "unite different forms of communication", and email is a crucial medium with which users communicate with us. This is why Insight is capable of both importing and sending email, and this is why Insight includes a complete email client build into it's web UI.

Insight's email capabilities are all controlled by Mailboxes inside the system. To help demonstrate the configuration and use of these Mailboxes, I have created a new tutorial video:

Mailboxes in Insight


Friday, June 11, 2010

Selenium 2 and WebDriver Extensions for .NET

If you have not had a change to play around with the Selenium 2 and WebDriver, let me assure you that it is worth checking out! We have been using WebDriver to help create unit tests for our new product, CodeSmith Insight, which is build using ASP.NET MVC2 for the back end and the ExtJS framework for our AJAX client.

Despite already being infinitely better than Selenium 1, it is still under development and in need of some polish. The good news is that by simply implementing a few extension methods, it can become a bit more versatile and a lot easier to work with.

Below are just definitions for the extension methods, please download the code from here.

LINQ Driver Extensions

The Find methods for WebDriver work with different selectors, but none of which support using LINQ expressions to search the resulting elements.

public static IWebElement FindElement(this IWebDriver driver, By by, Func<IWebElement, bool> predicate)
public static IEnumerable<IWebElement> FindElements(this IWebDriver driver, By by, Func<IWebElement, bool> predicate)

// Example
IWebElement el = driver.FindElement(By.CssSelector("input"), e => e.Value == "Save");

WaitFor Driver Extensions

WebDriver is great when working with elements that already exist on the page, but it is not so good at working with dynamic page elements. This is a slightly hacky solution, but I created added Wait method to the driver itself that just sleeps the current thread, and then I added WaitFor methods that sleep while waiting for elements to appear. This makes it much easier to work with elements that result from AJAX calls.

public static void Wait(this IWebDriver driver, int seconds = DefaultTimeout)
public static IWebElement WaitForElement(this IWebDriver driver, By by, Func<IWebElement, bool> predicate = null, int seconds = DefaultTimeout)
public static IEnumerable<IWebElement> WaitForElements(this IWebDriver driver, By by, Func<IWebElement, bool> predicate = null, int seconds = DefaultTimeout)

// Example
IWebElement el = driver.WaitForElement(By.CssSelector("div.success"), e => e.Text == "Cart saved!");


When switching between frames on a webpage, WebDriver actually changes the target of your Driver object. I created the FrameSwitcher class so that I could use a disposable pattern and reference my frame calls inside of a using block.

public static FrameSwitcher SwitchToFrame(this IWebDriver driver, By by)

// Example
using (driver.SwitchToFrame(By.Id("email_iframe")))


I did not feel that their documentation made it readily apparent, but you can still execute JavaScript against the web page. This is done by casting your WebDriver as IJavaScriptExecutor.

public static IJavaScriptExecutor GetJavaScriptExecutor(this IWebDriver driver)

// Example
var js = driver.GetJavaScriptExecutor();

Wednesday, May 19, 2010

Exception Driven Development and CodeSmith Insight

f customer facing issues are your top priority, then you are already adhere to the primary principle of Exception Driven Development.

Exception Driven Development

It's a pretty simple concept: Collect application data, especially regarding errors and crashes, and then focus on fixing those problems that your users are actually experiencing.

Following good development practices such as Test Driven Development, Automated Deployment, and rigorous QA, help us develop more reliable software. While these efforts can cost additional time and money, it is almost universally agreed that the return on investment (happy users working with a solid product) is worth every penny. So given all the things we do to prevent bugs from getting released, when a bug does finally make it into production, not fixing it right away would be negligent, irresponsible, and it would undermine all the work put into preventing bugs in the first place.

A great example of the ROI (return on investment) from using EDD is the Windows Error Reporting service. Microsoft has been recording errors from both their products and others for years now, and after analyzing the data they have come to some compelling conclusions:

"80 percent of customer issues can be solved by fixing 20 percent of the top-reported bugs. Even addressing 1 percent of the top bugs would address 50 percent of the customer issues."

What does all this mean? It means it's a really good idea to log errors, respond to user defect reports, and basically do everything you can to fix bugs that users are experiencing, ASAP!

So, it's good to ship buggy code? No, absolutely not! ...but sooner or later, it is going to happen; and when it does, it is your job to improve your software.

How do we do this? Use some form of exception and error reporting software. You need a central location to aggregate your application's errors and crashes.

While the concepts of EDD are not rocket science, the task can still take a little bit of work to achieve. Having the correct tools readily available makes the job much easier...

1) Error Reporting Services

I am saying that you need something to report errors, and then I'm suggesting that you probably want a preexisting tool to do that. Implementing your own solution to log errors is probably tempting, but it's not often the best idea. A few common flaws include: increasing time to market, a lack of standardization between products and other tools, and the ironic bugs in your bug tracker.

For reference, here are just a few of the better error reporting tools out there:

  • Exceptioneer

A major problem with the simplest (and thus most common) implementation of error reporting software, is that often it's just a log...a log that quickly starts to resemble the bottomless pit in the basement of any fantasy dungeon; you know, the one where even the bravest warriors, and savviest developers, fear to tread. (Please note, I am not trying to imply that the tools I mentioned above do this, I'm just saying that logs eventually stack up.)  A good solution to this is to organize captured errors into an issue tracking system...

2) Issue Tracking

Often issue tracker software gets merged with project management software, so without going into it too much I'll just say a few quick things:

  • Different exceptions may be the same bug.
  • Twenty occurrences of the same bug are still just one bug.
  • Two hundred occurrences of one bug does not necessarily make it higher priority than the twenty occurrences bug.

Having a system to help organize and prioritize your bugs is pretty vital to getting them fixed.

So far we have talked about capturing errors and organizing them, but what about bugs that don't crash or throw exceptions?

3) Help Desk

Not all bugs in software cause the application to blow up, nor do they always throw exceptions. Sometimes a human being (not something that we are used to working with in software) actually has to tell you "Hey, X is not working!" This kind of communication can be facilitated by email, feedback services, etc. The bottom line is that you need a way to collect feedback (include defect reports) from users; and then, ideally, you want to sync that information with whatever you are using to track issues/tasks.

Here are a few good help desk systems:

  • FogBugz
  • Smarter Track

So, if you like the concepts behind EDD, but don't like the idea of having to pay-for/integrate-with/learn-how-to-use/login-to/aggregate-data-from three different systems *GASPS FOR AIR*, than I have just the product for you!

CodeSmith Insight

A jack of all aforementioned trades, and a master of feedback aggregation, CodeSmith Insight was designed to unite these types of software systems.

Error Reporting Service

CodeSmith Insight provides a client that integrates with any .NET application. You just drop in one assembly reference, and add a few simple lines into your app.config, and then Insight will be wired up to automatically report all unhandled exceptions. The CodeSmith Insight client also makes it extremely easy to report any other information you want, whether that be creating a new case in the system, or adding additional information to a preexisting case.

Issue Tracker

All cases reported to CodeSmith Insight, whether they be crash reports, user feedback, or emails, are all saved in a central database hosted in the cloud. Insight automatically stacks duplicate errors that it receives, as well as allows you to merge issues together. All cases are completely searchable, and include a slew of information, including a complete history of actions taken on the case.

Help Desk

CodeSmith Insight can send and receive email, and it offers a robust email client built right into it's web UI. After a case is created in the system (whether that be a crash report or an email), you can then respond directly to a user regarding that case, and then go back and fourth from there. You can even send an update to a group of users experiencing the same issue and let them know the issue has been fixed.

I realize that as a CodeSmith employee I probably have a little bit of bias on subject, but I can say with all honesty and sincerity that CodeSmith Insight really is the perfect tool for Exception Driven Development. But you don't have to take my word for it; CodeSmith Insight is currently in beta, and is FREE! Go check it out, and decide for yourself.

Monday, May 3, 2010

MVC 2 Client Side Model Validation with ExtJS

One of the most exciting new features in MVC 2 is "Enhanced Model Validation support across both server and client". The new enhanced support allows for client side validation to be dynamically generated into a view quickly and easily using DataAnnotations attributes on models. This means that by simply dressing up your model properties with attributes, your web pages can instantly have dynamic client side validation; all generated and maintained for you by the MVC framework!

MVC 2 enhanced model validation really is a terrific new, what's the problem? Microsoft uses JQuery, we use ExtJS.

We here at CodeSmith absolutely love MVC, but many of features are designed to use JQuery, and we (for several different reasons) prefer to use the ExtJS Framework. This means that (as it stands) to use both we must reference both, and no one wants to download two complete AJAX frameworks just to view one webpage.

Good news, we fixed that!

Introducing: Ext.ux.MvcFormValidator

The MVC client side form validator dynamically generates a small blob of configuration for each form, and stores them in window.mvcClientValidationMetadata. Once the page loads up, the client side validator framework loads all these configs, and then starts monitoring the specified forms for validation.

What we have done is created an alternative form validation context (modeled after Sys.Mvc.FormContext) that loads the validation configuration, and requires no changes in the MVC generated code, but uses ExtJS as it's back end. This means that the only thing you have to do is reference Ext.ux.MvcFormValidator.js, and it will enable you to use the ExtJS Core for form validation instead of having to import MicrosoftMvcValidation and the other Microsoft AJAX libraries.


  • Requires only the ExtJS Core.
  • Implements all four default validators: Required, Range, StringLength, RegularExpression
  • Supports integration of custom validators with almost no code change.
    • Just update Sys.Mvc.ValidatorRegistry.validators to call Ext.Mvc.ValidatorRegistry.validators
  • Displays field messages as well as summary.
  • Extends Ext.util.Observable and uses events.
  • Lightweight; less than 300 lines of code.


Demo Solution:



public class ValidationDemoModel
    public string Name { get; set; }

    [Range(10000, 99999)]
    public string ZipCode { get; set; }

    [RegularExpression (@"\d{3}[-]?\d{3}[-]?\d{4}", ErrorMessage="The field Phone must be valid phone number.")]
    public string Phone { get; set; }

    public bool Accept { get; set; }


<script src="/Scripts/ext-core.js" type="text/javascript"></script>
<script src="/Scripts/Ext.ux.MvcFormValidator.js" type="text/javascript"></script>
<script src="/Scripts/CustomValidators.js" type="text/javascript"></script>


<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h2>Validation Demo</h2>
        <% Html.EnableClientValidation(); %>
        <% using (Html.BeginForm()) { %>
        <%= Html.LabelFor(m => m.Name) %>:
        <%= Html.TextBoxFor(m => m.Name)%>
        <%= Html.ValidationMessageFor(m => m.Name)%>
        <br />
        <%= Html.LabelFor(m => m.Phone) %>:
        <%= Html.TextBoxFor(m => m.Phone)%>
        <%= Html.ValidationMessageFor(m => m.Phone)%>
        <br />
        <%= Html.LabelFor(m => m.ZipCode) %>:
        <%= Html.TextBoxFor(m => m.ZipCode)%>
        <%= Html.ValidationMessageFor(m => m.ZipCode)%>
        <br />
        <%= Html.LabelFor(m => m.Accept) %>:
        <%= Html.CheckBoxFor(m => m.Accept)%>
        <%= Html.ValidationMessageFor(m => m.Accept)%>
        <br />
        <input type="submit" value="Submit" />
        <%  } %>

Controller Action

public ActionResult ValidationDemo()
    var model = new ValidationDemoModel();
    return View(model);

Custom Validator

Ext.Mvc.ValidatorRegistry.validators["acceptbox"] = function (rule) {
    return function (value, context) {
        return context.fieldContext.elements[0].checked === true;

Wednesday, March 10, 2010


The CodeSmith Tools (Shannon and Tom) are back on the road and delivering their first presentation of the new decade, in Houston!

If you or any developers that you know will be in the Houston area on Thursday, March 11th, you should come see us and learn more about PLINQO, the replace and enhance alternative for LINQ to SQL!

Houston Dot Net User Group
Topic: PLINQO - Supercharged LINQ To SQL
Speakers: Shannon Davidson & Tom DuPont
Date: March 11th @ 6:30 PM
Where: Microsoft Houston Meeting Facility
Address: One Briar Lake Plaza 2000 W. Sam Houston Pkwy. S. #350 Houston, TX 77042

Wednesday, February 17, 2010

Advanced PLINQO Future Queries, Part 2

The immediate uses of PLINQO Future Queries are readily apparent: Do you have multiple queries that need to be executed back to back? Add future to the back of them and save yourself a few database trips.

What might not be quite as easy to spot, are those places where using a more advanced design pattern can really enhance your application's performance. Here is an example of one of those patters.

Part 2: Pipeline Loader Patter

There are many times when an application needs to process a series of independent actions, each requiring its own set of database objects. If using LINQ to SQL (prior to PLINQO), this would mean that you need to either hard code the loading of all the necessary data in one place, or allow each action to load its own data in succession; thus leaving you with the options of bad abstraction, or lack of optimization; neither of which are very desirable.

Using the PLINQO Future Queries in conjunction with some simple interfaces, you can create a pipeline system that allows your actions to share one single data loading session. As usual, I feel that code narrates this best: 

Use Case

public void Test()
   var actions = new List<IPipelineLoader>
       new CaseAction { CaseId = "A", Title = "First New Title" },
       new CaseAction { CaseId = "A", Title = "Second New Title" },
       new UserAction { UserId = 1, Title = "Senior Tester" },
       new UserAction { UserId = 2, Title = "Junior Tester" }

   using (var db = new DemoDataContext())
       actions.ForEach(a => a.Load(db));
       actions.ForEach(a => a.Process());


public interface IPipelineLoader
   void Load(DemoDataContext db);
   void Process();


public class CaseAction : IPipelineLoader
   private FutureValue<Case> _futureCase;

   public string CaseId { get; set; }
   public string Title { get; set; }

   public void Load(DemoDataContext db)
       _futureCase = db.Case.ById(CaseId).FutureFirstOrDefault();

   public void Process()
       _futureCase.Value.Title = Title;

public class UserAction : IPipelineLoader
   private FutureValue<User> _futureUser;

   public int UserId { get; set; }
   public string Title { get; set; }

   public void Load(DemoDataContext db)
       _futureUser = db.User.ById(UserId).FutureFirstOrDefault();

   public void Process()
       _futureUser.Value.Title = Title;

Wednesday, February 10, 2010

Advanced PLINQO Future Queries, Part 1

The immediate uses of PLINQO Future Queries are readily apparent: Do you have multiple queries that need to be executed back to back? Add future to the back of them and save yourself a few database trips. What might not be quite as easy to spot, are those places where using a more advanced design pattern can really enhance your application's performance. Here is an example of one of those patters.

Part 1: Entity Loaders

When retrieving entities from the database you can not always use DataLoadOptions to populate the complete object model in a single trip to the database. Having to make multiple trips to populate one type of entity can be costly, and this can be compounded by the need to populate a series of different entities at the same time.

One solution is to create a loader class for your entities that use future queries to get their data, as this is a very reliable means with which to ensure that no extra unnecessary database calls are execute. Also, by creating an interface for these loader classes, you can use a generic piece of code to load any number of entities of any number of data types.

Use Cases

public static Case Test(int id)
    var loader = new CaseTagLoader(id);
    return loader.Entity;

public static List<Case> Test(int[] ids)
    var loaders = new List<CaseTagLoader>();
    foreach (var id in ids)
        loaders.Add(new CaseTagLoader(id));

    using (var db = new BlogDataContext())
        loaders.ForEach(l => l.Load(db));

    return loaders.Select(l => l.Entity).ToList();


public interface ILoader<T>
    void Load();
    void Load(BlogDataContext db);
    T Entity { get; }
    bool IsLoaded { get; }


public class CaseTagLoader : ILoader<Case>
    private readonly int _caseId;
    private FutureValue<Case> _futureCase;
    private IEnumerable<CaseTag> _caseTags;
    private IEnumerable<Tag> _tags;
    private Case _case;

    public Case Entity
            if (!IsLoaded)
                return null;

            if (_case == null)

            return _case;

    public bool IsLoaded { get; private set; }

    public CaseTagLoader(int caseId)
        _caseId = caseId;
        IsLoaded = false;

    public void Load()
        if (IsLoaded)

        using (var db = new BlogDataContext())
            db.ObjectTrackingEnabled = false;

    public void Load(BlogDataContext db)
        if (IsLoaded)

        _futureCase = db.Case.ById(_caseId).FutureFirstOrDefault();
        var caseTagQuery = db.CaseTag.ByCaseId(_caseId);
        _caseTags = caseTagQuery.Future();
        _tags = db.Tag
            .Join(caseTagQuery, t => t.Id, ct => ct.TagId, (t, ct) => t)

        IsLoaded = true;

    private void CreateCase()
        _case = _futureCase.Value;

        foreach (var caseTag in _caseTags)
            caseTag.Tag = _tags.First(t => t.Id == caseTag.TagId);

Real Time Web Analytics