Blog Stats
  • Posts - 56
  • Articles - 0
  • Comments - 6
  • Trackbacks - 0

 

Monday, January 20, 2014

Formatting Command Output on the TFS Build Report


The Team Foundation Server build can include command output in the report log or summary by using the stdOutput and errOutput variables in the WriteBuildMessage or WriteCustomSummaryInformation activities. I’ve often been frustrated with bad formatting that you get.  However, I found that if you simply use String.Format with stdOutput or errOuput, you get much better formatting. For example, with String.Format("{0}", stdOutput) you get correct line returns.

Tuesday, January 14, 2014

Making Agile Work on Large, Complex Projects


Our TFS Austin User Group hosted this presentation last month given by Mike Haze, Director of Product Management for Volusion.  It was one of the best meetings we’ve had.  Here’s the abstract and a link to the recording of the meeting.

Recording Link: http://usergroup.tv/videos/making-agile-work-on-large-complex-projects 

Presentation: Making Agile Work on Large, Complex Projects

There is a widely accepted myth that extremely complex software platforms, committed delivery schedules, large development teams, and dynamic business environments are in direct opposition to agile development processes and tools and if your product / project fall into one of these categories you will struggle with fully embracing agile methodologies. This is simply not the case, you can fully adopt and leverage an agile methodology through out the full product lifecycle, gaining all the advantages and leverage the tools your teams are already using today and are tightly integrated into the development process. We will explore how Volusion is currently leveraging TFS throughout the complete product lifecycle and discuss some of the key challenges and successes that we've experienced along the way.

Speaker

Mike Haze is the Director of Product Management at Volusion. Mike holds a number of patents including "System and method for migration of digital assets" and "System and method for self-provisioning of virtual images." Beginning his career as a Software Developer, Mike has served as a Product Experience Strategist for Dell and the Director of Product Development and Innovation for Dun and Bradstreet.

Volusion is a leading e-commerce software development company based in Austin with $12B in merchant sales worldwide, over 40,000 online stores and over 450 employees. Volusion has won the Austin Business Journal's award for Best Places to Work for 2012 and 2013.

TfsUGMikeHaze1 TfsUGMikeHaze2

Wednesday, July 24, 2013

TFS 2012 API Create Alert Subscriptions


There were only a few post on this and I felt like really important information was left out:

  1. What the defaults are
  2. How to create the filter string

Here’s the code to create the subscription.

Get the Collection

public TfsTeamProjectCollection GetCollection(string collectionUrl)
        {
            try
            {
                //connect to the TFS collection using the active user
                TfsTeamProjectCollection tpc = new TfsTeamProjectCollection(new Uri(collectionUrl));
                tpc.EnsureAuthenticated();
                return tpc;
            }
            catch (Exception)
            {
                return null;
            }
        }

Use Impersonation

Because my app is used to create “support tickets” as stories in TFS, I use impersonation so the subscription is setup for the “requester.”  That way I can take all the defaults for the subscription delivery preferences.

public TfsTeamProjectCollection GetCollectionImpersonation(string collectionUrl, string impersonatingUserAccount)
        {
            // see: http://blogs.msdn.com/b/taylaf/archive/2009/12/04/introducing-tfs-impersonation.aspx
            try
            {
                TfsTeamProjectCollection tpc = GetCollection(collectionUrl);
                if (!(tpc == null))
                {
                    //get the TFS identity management service (v2 is 2012 only)
                    IIdentityManagementService2 ims = tpc.GetService<IIdentityManagementService2>();

                    //look up the user we want to impersonate
                    TeamFoundationIdentity identity = ims.ReadIdentity(IdentitySearchFactor.AccountName, 
                        impersonatingUserAccount, 
                        MembershipQuery.None, 
                        ReadIdentityOptions.None);

                    //create a new connection using the impersonated user account
                    //note: do not ensure authentication because the impersonated user may not have 
                    //windows authentication at execution
                    if (!(identity == null))
                    {
                        TfsTeamProjectCollection itpc = new TfsTeamProjectCollection(tpc.Uri, identity.Descriptor);
                        return itpc;
                    }
                    else
                    {
                        //the user account is not found
                        return null;
                    }
                }
                else
                {
                    return null;
                }
            }
            catch (Exception)
            {
                return null;
            }
        }

Create the Alert Subscription

public bool SetWiAlert(string collectionUrl, string projectName, int wiId, string emailAddress, string userAccount)
        {
            bool setSuccessful = false;
            try
            {
                //use impersonation so the event service creating the subscription will default to 
                //the correct account: otherwise domain ambiguity could be a problem
                TfsTeamProjectCollection itpc = GetCollectionImpersonation(collectionUrl, userAccount);

                if (!(itpc == null))
                {

                    IEventService es = itpc.GetService(typeof(IEventService)) as IEventService;

                    DeliveryPreference deliveryPreference = new DeliveryPreference();
                    //deliveryPreference.Address = emailAddress;
                    deliveryPreference.Schedule = DeliverySchedule.Immediate;
                    deliveryPreference.Type = DeliveryType.EmailHtml;

                    //the following line does not work for two reasons: 
                    //string filter = string.Format("\"ID\" = '{0}' AND \"Authorized As\" <> '[Me]'", wiId);

                    //1. the create fails because there is a space between Authorized As
                    //2. the explicit query criteria are all incorrect anyway
                    //   see uncommented line for what does work: you have to create the subscription mannually 
                    //   and then get it to view what the filter string needs to be (see following commented code)
                    
                    //this works
                    string filter = string.Format("\"CoreFields/IntegerFields/Field[Name='ID']/NewValue\" = '12175'" + 
                                                    " AND \"CoreFields/StringFields/Field[Name='Authorized As']/NewValue\"" + 
                                                    " <> '@@MyDisplayName@@'", projectName, wiId);

                    string eventName = string.Format("<PT N=\"ALM Ticket for Work Item {0}\"/>", wiId);

                    es.SubscribeEvent("WorkItemChangedEvent", filter, deliveryPreference, eventName);

                    ////use this code to get existing subscriptions: you can look at manually created 
                    ////subscriptions to see what the filter string needs to be
                    //IIdentityManagementService2 ims = itpc.GetService<IIdentityManagementService2>();
                    //TeamFoundationIdentity identity = ims.ReadIdentity(IdentitySearchFactor.AccountName, 
                    //    userAccount, 
                    //    MembershipQuery.None, 
                    //    ReadIdentityOptions.None);
                    //var existingsubscriptions = es.GetEventSubscriptions(identity.Descriptor);

                    setSuccessful = true;

                    return setSuccessful;
                }
                else
                {
                    return setSuccessful;
                }
            }
            catch (Exception)
            {
                return setSuccessful;
            }
        }

Thursday, May 30, 2013

Adding a user to the TFS "Project Collection Service Accounts" group


Some types of work item updates require the updating account to be in the “Project Collection Service Accounts” group. For example, the work item server - var wiSrv = tpc.GetService<WorkItemServer>();

You have to use the command line to do this. Here’s an example:

tfssecurity /g+ "Project Collection Service Accounts" n:{domain\user} /collection:{your collection url}

Wednesday, May 8, 2013

Semantic Versioning


In a discussion about software product versioning, the post on Semantic Versioning came up. I found the essay to reflect commonly recognized best practices. However, the value of the essay is in the concise and rigorous rules for incrementing version numbers. 

Monday, March 11, 2013

Retain and Set Posted Checkbox Value in the MVC 4 Controller


I believe this is a bug where only the checkbox value is not retained when passing the model from the view to a post method.

Model

public bool CoreField { get; set; }

View

@model List<Model>

@Html.CheckBoxFor(m => m[i].CoreField, 
 new { @id = "cbCoreField" + i })@Html.HiddenFor(m => m[i].CoreField)
@Html.Hidden("fIsCore", 
 null, new { @id = "hIsCore" + i }) 
//has to use separate list instead of model due to MVC bug

Note the use of “fIsCore” as a separate list used to pass changes to the checkbox into the post method and the use of ModelState.SetModelValue to set the checkbox value for redisplay when the model is invalid (i.e. adding a row where the model validation rules for some other field has been violated.

Controller Post Method

[HttpPost]
        public ActionResult Index(List<Model> model, List<bool> fIsCore)
        {
            if (!(model == null))
            {
                if (ModelState.IsValid)
                {
                    int i = 0;
                    foreach (var item in model)
                    {
                        item.CoreField = fIsCore[i];
                        i++;

                        if (!(item.ID == 0))
                        {
                            db.Entry(item).State = EntityState.Modified;
                        }
                        else
                        {
                            db.Model.Add(item);
                        }
                    }
                    db.SaveChanges();
                }
                else
                {
                    //ModelState.Clear(); // not needed now
                    int i = 0;
                    foreach (var item in model)
                    {
                        string key = string.Format("[{0}].CoreField", i);
                        item.CoreField = fIsCore[i];
                        ModelState.SetModelValue(key, 
                            new ValueProviderResult(item.CoreField, 
                                "",
                                CultureInfo.InvariantCulture));
                        i++;
                    }

                    return View(model);
                }
            }
            return RedirectToAction("Index");
        }

Tuesday, March 5, 2013

Using the DIR command in the TFS 2010/12 Team Build Invoke Process Activity


I found that using a loop activity to build up a filtered list clutters up the build report with repetitions of activities inside the loop. I could write a custom activity to create the list. However, rather than have to write and maintain code, it’s possible to use the DOS DIR command in the Invoke Process activity.

First, because there is no dir.exe file, you must execute a windows shell. I used “cmd” as the FileName value. 

image

Then in the Arguments value I use the shell /c switch followed by the DIR  command. It’s also possible to exclude file types from the list.

String.Format("{0} ""{1}"" {2}",  "/c dir",  BinariesDirectory,  "/b  |findstr /vi "".zip""")

Thursday, January 24, 2013

Setting HTML.CheckBoxFor Values in MVC 4 View from Json return using jQuery Ajax Script


I’m an MVC/jQuery noobie and this one really got my goat for while. The problem was handling the bit field in SQL Server which was treated as a checkbox field in MVC.

Here’s how you can set a checkbox value based the the user’s selection from dropdown list on the same view:

Model:

public string Field_Ref_Name { get; set; } // the dropdown list 
public bool CoreField { get; set; } // the checkbox

Controller:

From the Get ActionResult method to populate the dropdown list.  
ViewBag.projectConditionWitRefFieldsDropdownList = 
                new SelectList(refFieldList, selectedField);

The JsonResult method called by the jQuery script in the view.

public JsonResult GetFieldAttributes(string selectedField)
        {
            string a = selectedField;
            bool b = false;
            if (!(selectedField.Contains("some value")))
            {
                b = true;
            }
            
            var dataParms = new { aA = a, aB = b };
            return Json(dataParms, JsonRequestBehavior.AllowGet);
        }

View:

The script, which gets the bool from the Json result and sets the checkbox. Note I had to use the “click” event to set the checkbox. All efforts to use some sort of checked true or false approach failed. The trick is the conditional logic of the data.aB boolean return value.

<script type="text/javascript">
     $(function() {
            $("#ddlField").change(function() {
                var selectedItem = $(this).val();
                $.ajax({
                    cache: false,
                    type: "GET",
                    url: "@(Url.Action("GetFieldAttributes", "ConditionDetails"))",
                    data: { "selectedField": selectedItem},
                    success: function (data) {
                        $("#DataType").val(data.aA);
                        if (data.aB) {
                            var chk = $('#CoreField').is(':checked');
                            if (!(chk)) {
                                $("#CoreField").click();
                            }
                        }
                        else {
                            var chk = $('#CoreField').is(':checked');
                            if (chk) {
                                $("#CoreField").click();
                            }
                        }
                    },
                    error:function (xhr, ajaxOptions, thrownError){
                        alert('Failed to return core and type field attributes.');

                    }  
                });
            });
        });
    </script>

From the razor code to display the page.

@using (Html.BeginForm()) 
        {
            @Html.ValidationSummary(true)

            <fieldset>
                <legend>Condition_Details</legend>

                <div class="editor-label">
                    @Html.LabelFor(model => model.Field_Ref_Name)
                </div>
                <div class="editor-field">
                    @if (@ViewBag.projectFilterSelected)
                    {
                        @Html.DropDownListFor(model => model.Field_Ref_Name, 
                        ViewBag.projectConditionWitRefFieldsDropdownList as SelectList, 
                            string.Empty, new { @id = "ddlField", style = "font-size: .85em; font-weight: normal;" })
                    }
                    else
                    {
                        @Html.EditorFor(model => model.Field_Ref_Name)
                    } 
                    @Html.ValidationMessageFor(model => model.Field_Ref_Name)
                </div>

                <div class="editor-label">
                    @Html.LabelFor(model => model.DataType)
                </div>
                <div class="editor-field">
                    @Html.EditorFor(model => model.DataType)
                    @Html.ValidationMessageFor(model => model.DataType)
                </div>

                <div class="editor-label">
                    @Html.LabelFor(model => model.CoreField)
                </div>
                <div class="editor-field">
                    @Html.CheckBoxFor(model => model.CoreField)
                    @Html.ValidationMessageFor(model => model.CoreField)
                </div>

            </fieldset>
        }

Wednesday, January 2, 2013

MVC ModelState.IsValid Fails When Reserved Word Used for Table Name


When a table name uses a reserved word: for example, “Action,” model validation will fail even though the model is valid. The error message from ModelState is:

“The parameter conversion from type 'System.String' to… …type failed because no type converter can convert between these types.”

For example, if a table named Action has a related table, Action_Details, where an Action may have one to many Action_Conditions, you may have a model class for Action_Details that looks like this:

    public class Action_Details
    {
        public int ID { get; set; }
        public int Action_ID { get; set; }
        public string Value { get; set; }
        public string ParentField { get; set; }
        public string ChildField { get; set; }
        public Nullable<bool> Inherit { get; set; }
        public virtual Action Action { get; set; } 
    }

My solution was to change the related table class

    public class Action_Details
    {
        public int ID { get; set; }
        public int Action_ID { get; set; }
        public string Value { get; set; }
        public string ParentField { get; set; }
        public string ChildField { get; set; }
        public Nullable<bool> Inherit { get; set; }
        //public virtual Action Action { get; set; } 
        // ** as "Action" is a reserved word, I had to use the 
        // ** fully qualified class name (i.e. UI.Models.Action) 
        // ** AND change the object name from Action to Action1
        public virtual UI.Models.Action Action1 { get; set; }
    }

Thursday, December 27, 2012

LINQ (C#) Many to Many Query using Entity Framework


Using a MVC 4 Code First project I noticed that my intermediate table between a many to many relationship was missing. In other words, Table 1 has many Table 3, and Table 3 has many Table 1. In the database schema, Table 2 is my intermediate table:

  • Table 1 has many Table 2 and Table 2 has only one Table1
  • Table 3 has many Table 2 and Table 2 has only one Table3, as follows:

 

 EF

However, as I stated earlier, I did not have a model class for Table 2.  At first I was miffed, but once I was able to craft the query, I was impressed at how easy EF made this.  I wanted to get a listing of Table 3 fields for a specific ProjectID (pID) in Table 1.  Here’s the query :)

var Table3Qry = from t3 in db.Table3 
from t2 in t3.Table1.Where(x => x.ProjectID == pID) 
select new { t3.foo1, t3.foo2, t3.foo3 };
 

 

Copyright © Bob Hardister