Request a topic or
contact an Arke consultant
404-812-3123
ASP.NET

Arke Systems Blog

Useful technical and business information straight from Arke.

About the author

Author Name is someone.
E-mail me Send mail

Recent comments

Archive

Authors

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2024

The 5 minute MVC CMS

[Code at the bottom]

 

I had a client that needed a solution that could meet the following requirements:

  • Must be able to add and remove page from their website
  • Must be able to change the content of each of the pages
  • Users of the website would need to be able to have a logon state
  • The pages would need to show up in the navigation menu
  • Must be able to do all of this without using a developer, re-engineering their site, installing a CMS system, or taking more than 4 hours of custom development time. 

 

They also had the following merits:

  • Those who would change the content were proficient in Html, CSS, and basic web technology
  • All pages would have the same layout
  • Existing website written in ASP.NET MVC

 

In the end, the content managers had access to a "PartialPages" folder that set under the main site on the webserver.  The managers could add, remove, and edit these files.  They could also update the SiteMap.xml file for these pages, allowing for the navigation menu to update based on their changes. 

 

CODE:

 

 AreaRegistration - Routing:

 

context.MapRoute(
    "FiveMinuteCMS_Default",  // Route name
    "FiveMinuteCMS/{id}",      // URL with parameters
    new { controller = "FiveMinuteCMS", action = "Pages", id = UrlParameter.Optional }
);

 

 Controller:

 

public ActionResult Pages(string id)
{
    PagesModel model = new PagesModel();
    model.Page = System.IO.File.ReadAllText(HttpContext.Server.MapPath
        ("~/PartialPages/" + id + ".html"));       
    return View(model);
}

 

Model:

 

public class PagesModel
{
    public string Page { get; set; }
}

 

Pages View:

 

@model MySampleProject.Areas.FiveMinuteCMS.Models.PagesModel
@{
    // Any layout will do
    Layout = "~/Areas/FiveMinuteCMS/Views/Shared/_Content.cshtml";
}

@Html.Raw(Model.Page)

 

 

Sample Partial Page file:

 

<div id=”PartialContentBody”>               
    <span>Hello World</span>
</div>

 

For the site map and navigation, I used Maarten Balliauw's MvcSiteMapProvider


Tags: , ,
Categories: ASP.NET | MVC | CMS
Posted by Trenton Adams on Tuesday, August 20, 2013 4:46 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Render unto c#

When it comes to CMS development (among other things in life) I’m a big fan of granularity and reusability. I believe that in a good solution architecture, both content and code are managed in discreet, granular pieces, which promotes both consistency and reusability.

That’s why it bothers me that so many Sitecore professionals react to the word “rendering” the same way a horse would. To my mind, a good solution has a geometric ratio of layouts to sublayouts, and sublayouts to renderings. Yet I see projects that are extremely sublayout-centric, with content IA and logic governing page layout.

Sitecore’s arsenal of presentation management tools, such as layout details, placeholder settings and rendering parameters, allows us as developers to truly empower our content owners. When implemented well, these features give the content authors what I like to call “controlled control” over their pages.

Unfortunately, we often see solutions where sublayouts are the dominant presentation component. Sublayouts are far less efficient than renderings, both in terms of performance and management. With sublayouts, there are all the ascx files to manage and deploy. And using sublayouts to present content seems to promote having a single component (sublayout) present multiple fields. This can dramatically reduce the solution’s flexibility – or worse, lead to having multiple components that are slight variations on each other, or have cumbersome logic (often wired to “control” fields in the content) to suppress content or change the presentation behavior.

Worse, some solutions rely almost entirely on the content tree to govern layout, such as by having fields in page templates that change the behavior of the layout, and/or by having sets of child items that code “bubbles up” into the page when they are present.

So why are renderings so often left out of solution architecture? I suspect that the problem lies in Sitecore training. Although Sitecore trainers do point out the different ways that renderings can be created, they tend to use XSL as the example technology in class (probably because it’s quicker to demonstrate --the trainer can show changes to an XSL rendering immediately, without compiling). Regrettably, this leaves many with a linkage in their minds between renderings as a Sitecore artifact and XSL as a technology. The oft-maligned rendering can be implemented using multiple technologies, yet many, many developers believe that renderings can only be developed using XSL.

I’m not going to wade into the great XSL debate here. I personally like XSL, but I rarely use it in Sitecore projects, for a number of reasons that I’ll get into that in a separate post. Suffice it to say that many developers, even if they know XSL, want to avoid it if for no other reason than to make their projects sustainable. XSL is a far more rare skill than c#, so it makes sense to ensure that future developers will be able to extend and maintain the project. And since there’s this misconception that “renderings = XSL”, the “baby” of a rendering-based architecture gets thrown out with the “bathwater” of XSL. And that’s a shame.

So let’s set the record straight. Renderings can and should be developed in c#. Actually, John West points out in his book “Professional Sitecore Development,” there are four types of renderings:

  1. XSL renderings
  2. Method renderings
  3. URL renderings
  4. Web Controls

Of these, Web Control renderings – controls that are implemented entirely in code and deployed in assemblies – are the least used yet most useful presentation component available. (In my next post, I’ll delve into the anatomy of a web control rendering.)

As a simple example, consider a page with a “core content” (“body”) area consisting of a title, a main image, and some body text. These three fields are defined in the page’s template. One way of handling this is to create a sublayout that renders these fields to the output (hopefully, at least, using field renderers). But what happens when the author does not want a title on a particular page (this is a simple example, so let’s not quibble over usability or SEO). Sure, the sublayout’s code-behind could suppress the <h1> when the field is empty or null. Or we could have a checkbox in the template to suppress the title.

What if the content author wants something other than a main image, like a flash or a video? What if they need to insert something between the image and the body? And after the body, they might need set of “spots” to point to other content?

So we might resort to having different sublayouts with variations on the content. This can lead to an unmanageable mess, creating confusion as to which sublayout does what. It also leads to redundant code, which makes ongoing maintenance and modification to the site much more challenging.

Another solution would be to put fields in the template to suppress content, or add content, or modify the presentation of content. Or we might create templates for child items that, when present, are bubbled up into the page. This puts management of presentation into the data. Sitecore is so well architected to give us excellent separation of content and presentation, so why fight that and force content to manage presentation?

I much prefer solutions that have very “light” layouts and sublayouts, with lots of renderings bound to placeholders. For the simple example, I would have a separate rendering each for title, main image, and body text, which I would bind to a placeholder in the sublayout for the core content area. Standard values in the item’s template would bind them by default, but the content owner would have the freedom to change the presentation as required. We can use features like thumbnails, rendering parameters, placeholder settings and compatible renderings both to assist them in the layout process, and to enforce brand or visual design requirements.

Most of my layouts and sublayouts are little more than div’s and placeholders. They exist to manage the geometry of the page or of regions of the page, not to present the actual content. Renderings bound to placeholders actually emit the content. Rendering parameter templates allow the editor to influence the source and behavior of the content in the page. Now, control over presentation and layout are managed in the presentation layer, and content is managed in the content layer. The design of the IA can break the content down into more manageable chunks, promoting reusability and avoiding redundancy.

To be fair, there are times when sublayouts are a better choice than renderings. For example, for forms or other situations where postback is required, I refer to use sublayouts (ascx) controls.. Also, in cases where the parts of the page structure are immutable, it is more efficient to statically bind renderings into sublayouts (by including them in the sublayout markup). This is fine for cases like fixed headers and footers, or when the site design requires an element (like a title) to always be present. There are also rare occasions when I allow a folder of child items to bubble up into a page, but even then, I usually use a placeholder-bound rendering to do the bubbling.

A highly granular architecture, which maintains separation of content from presentation, is hugely empowering to both developers and content editors. It promotes reusability of both code and content, shifts much of the responsibility for page assembly from code to configuration, and empowers editors with more control over the layout of their pages.


Posted by Andy Uzick on Thursday, January 24, 2013 6:52 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Passing query to Custom Google Search on your webpage

Stackoverflow got me the answer to this.

If you're using Custom Google Search on your website and you have a small textbox on each page that is supposed to 'pass' that query to your search page (ultimately passed into your Google search post action), you should do the following: 

Instead of the code Google provided you with to embed, take your unique key provided by Google and place it in the '***my key***') setting of the first code snippet below.  What this is doing is creating a new instance of your custom google search with your unique key and drawing out the results (*cse*) and forcing it to submit the action (execute) with your parameters ($q). 

 

    google.load('search', '1', { language: 'en' });

    function OnLoad() {
        var customSearchControl = new google.search.CustomSearchControl('009045124056933145342:5wmgo53sugc');
        customSearchControl.setResultSetSize(google.search.Search.FILTERED_CSE_RESULTSET);
        customSearchControl.draw('cse');
        customSearchControl.execute(search_value);
    }
    google.setOnLoadCallback(OnLoad);

 

On my click event for my search text box on every page, I sanitized the query (this is a very basic sanitize, you should alter as necessary to protect your site!) and appended those parameters to the query string being sent to my search results page.

private String SanitizeUserInput(String text)
        {
            if (String.IsNullOrEmpty(text))
                return String.Empty;

            String rxPattern = "<(?>\"[^\"]*\"|'[^']*'|[^'\">])*>";
            Regex rx = new Regex(rxPattern);
            String output = rx.Replace(text, String.Empty);

            return output;
        }

        protected void Button1_Click(object sender, ClickEventArgs e)
        {

            Response.Redirect(
                String.Format(
                Page.ResolveUrl("~/{MySearchPage}.aspx?q={0}"),
                    HttpUtility.UrlEncode(SanitizeUserInput({mysearchtextbox}.Text.Trim()))
                    ),
                false
                );
        }



On my search results page, I remove those parameters with the below function and pass them into my $q variable.

function getQuerystring(key, default_) {
if (default_ == null) default_ = "";
key = key.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
var regex = new RegExp("[\\?&]" + key + "=([^&#]*)");
var qs = regex.exec(window.location.href);
if (qs == null)
return default_;
else
return qs[1];
}


Posted by Nicole Rodriguez on Wednesday, August 24, 2011 9:36 PM
Permalink | Comments (0) | Post RSSRSS comment feed

DotNetNuke PurgeScheduleHistory

DotNetNuke runs a task to purge it’s schedule history; however, the stored procedure that does this has performance problems that will cause deadlocks on a high traffic website.

The query that the PurgeScheduleHistory stored procedure ships with is:

DELETE FROM dbo.ScheduleHistory
FROM dbo.Schedule s
WHERE (
  SELECT COUNT(*)
  FROM dbo.ScheduleHistory sh with (nolock)
  WHERE sh.ScheduleID = ScheduleHistory.ScheduleID
  AND sh.StartDate >= ScheduleHistory.StartDate
) > s.RetainHistoryNum
AND s.RetainHistoryNum <> -1
AND s.ScheduleID = ScheduleHistory.ScheduleID

Anytime you write a query that does a delete from a select you run the risk of deadlock.  Also, whenever you do a sweeping delete on a table you can escalate to a page lock, have trouble with locking indexes, and end up in a deadlock scenario again.  On the surface this doesn’t sound like a deadlock scenario, but you have to understand the way locks affect indexes and how locks escalate in SQL Server—you might be surprised to learn a simple SELECT query can deadlock under the right conditions also (http://stackoverflow.com/questions/661908/sql-server-deadlocks-between-select-update-or-multiple-selects).

The fix to this stored procedure follows the same approach Microsoft used to fix the DeleteExpiredSessions stored procedure that ships with ASP.NET (http://support.microsoft.com/kb/973849).

The following query should replace the PurgeScheduleHistory stored procedure:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[PurgeScheduleHistory]

AS

SET NOCOUNT ON
SET DEADLOCK_PRIORITY LOW

create table #T (ID int not null primary key)

insert into #T
select schedulehistoryid from (
select s.ScheduleID, sh.schedulehistoryid, rank() over (partition by s.scheduleid order by sh.startdate) rn, RetainHistoryNum
from ScheduleHistory sh WITH (READUNCOMMITTED)
join Schedule s WITH (READUNCOMMITTED) on s.ScheduleID = sh.ScheduleID
where s.RetainHistoryNum <> -1) a
where rn > RetainHistoryNum

DECLARE ESC CURSOR LOCAL FORWARD_ONLY READ_ONLY
FOR SELECT ID FROM #T

declare @ID int

open ESC

fetch next from ESC into @ID

WHILE @@FETCH_STATUS = 0
    BEGIN
        DELETE FROM ScheduleHistory WHERE ScheduleHistoryID = @ID
        FETCH NEXT FROM ESC INTO @ID
    END

CLOSE ESC

DEALLOCATE ESC

drop table #T

Thanks to David Eison for finding this solution.


Categories: SQL Server | ASP.NET | DotNetNuke
Posted by Eric Stoll on Wednesday, February 16, 2011 1:13 AM
Permalink | Comments (0) | Post RSSRSS comment feed

CRM and ViewState

Microsoft Dynamics CRM 4.0 doesn’t use ViewState, or Sessions.  Indeed, they are disabled in the web.config file.  This can be a bit of a surprise to an ASP.NET developer working on a custom page in the /ISV directory. 

Three possible solutions on ViewState:

1) Don’t use ViewState. 

This requires re-initializing any data with every postback.  One thing you can do is minimize postbacks.  You’ll see CRM does this in many places by encouraging the use of Javascript and popping extra windows to handle immediate-response things like filling in or validating a lookup field.  If you are displaying a grid, it will be empty after postback because it wasn’t re-populated from viewstate, so you need to repopulate it on every request – which means you also need to deal with the possibility that values may have changed due to another user editing records in the meantime.  Some simple strategies to start with are to refer to records by guids instead of by row numbers and to minimize updates to only the fields your user actually changed, and to code defensively.

2) You can enable viewstate for a particular page by adding it to the page directive at the top of the page:

<%@ Page . . . EnableViewState="true" . . .

Note that if you have a server cluster and use viewstate, you’ll either need to set a machine key in web.config or else disable viewstate validation with another Page directive:

<%@ Page . . . EnableViewState="true" EnableViewStateMac="false" %>

Disabling ViewStateMac means your users will be able to tamper with the viewstate, so just keep that concern in mind if you have custom permissioning rules in your app beyond CRM’s built in permissioning.

3) You can enable viewstate for all of your ISV pages by setting it up as its own app. 

Create a virtual directory under /ISV, point it to your pages, and give yourself a web.config that sets enableviewstate for your pages (and a machine key).  See, for example, this guide at xrmlinq.

Remember that if you’re going to use viewstate, you should keep an eye on viewstate size ; perhaps you don’t want to transfer a megabyte of serialized grid data with every page load.  You can see how big viewstate is by viewing source on your page, or by adding some code to your pages (If Request.IsLocal is true and DEBUG is defined, I tack on a label with the size from LosFormatter; see example code at scottonwriting ); but in general, if you use viewstate on a grid or listbox, you’re going to be storing a lot of data.  If you’d rather repopulate your grid or listbox with every request instead of serializing their data, simply databind them before viewstate begins being tracked - during init instead of during load.  See ASP.NET Page Lifecycle.

As for Sessions, I would recommend avoiding them.  You’ll probably have some real headaches if you need to support the offline client and rely on sessions, and it can be hard to anticipate (and test for) how sessions will be affected by one user popping several windows open.  In general, if data is ephemeral it can be handled well by viewstate.  If data is not ephemeral, you probably want to be storing it in a database.  So pass on session.


Posted by David Eison on Tuesday, October 19, 2010 3:04 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Silverlight Issues

Silverlight often feels a bit much like a work in progress, other times just like parts weren’t thought through all the way.  I thought I’d post a few examples of the sorts of things we’ve run into while developing with it.  Main lesson is, somehow you will need to plan for the unplanned.

For example, in the global application error handler:

Exception tmp = e.ExceptionObject;
if (tmp != null)
{
    while (tmp.InnerException != null)
    {
        tmp = tmp.InnerException;
    }

    // Silverlight defect where File stream can error out if file open failed due 
    // to an open file error, then later the garbage collector tries to clean up
    // the stream.  Can't be reliably handled at regular code level because we don't
    // get back a stream to operate on due to the file method erroring out.
    if (tmp is InvalidOperationException)
    {
        // string parse is bad and fragile, but I can't find any way to get the error code
        // this should be error code -2146233079 per Reflector, but, well.
        if (tmp.Message.Contains("UI Thread") && tmp.Message.Contains("System.Windows.SaveFileStream.Dispose"))
        {
            // ignore
            return;
        }
    }
. . .

Forums suggest this one may have been fixed in 4.

My favorite issue is a layout issue: If you want a label next to a textbox, you put it in a horizontal stackpanel.  But horizontal stackpanels don’t constrain themselves horizontally.  Bottom line?  Your labels won’t wrap and will clip instead.   Solution?  Specify fixed widths on your labels.  I wonder if nobody did a form with labels next to textboxes and a dynamic layout all during the testing and design phase.

We also have these gems:

                // NOTE: A "Dialogs must be user initiated" error can crop up if running under the debugger.
                // It is a spurious error and will not happen when not using the debugger.
                // See http://forums.silverlight.net/forums/t/82454.aspx

 

// firefox doesn't render our silverlight right in an iframe.
function isfirefox() {
    if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)) {
        return true;
    } else {
        return false;
    }
}

 

/*
    This service has to be activated using basicHttp because Silverlight cannot 
    function against wsHttp.  
*/

 

        //sizechanged doesn't fire properly when column widths are changed, tabs need to
        // be hidden and refreshed for it to update... also, this can be fired
        // too early, before the column width has really changed. Layoutupdated seems to mostly work, though.

 

// assigning width directly is sub-optimal because
// it won't handle a situation where this column should be bigger
// than the main column... but the grid doesn't redraw properly
// if we don't.  Tried forcing just minwidth to work 
// with invalidatemeasure, invalidatearrange,
// measure, arrange, and updatelayout, all of which redraw the column
// header right but not the column data.

Categories: ASP.NET | Silverlight
Posted by David Eison on Sunday, September 26, 2010 5:59 PM
Permalink | Comments (0) | Post RSSRSS comment feed

CRM and prepended org names

Customers with on-premise or installations of CRM will be familiar with seeing /ORGNAME/ as the root directory in their CRM URLs. 

Microsoft added this feature so that one CRM installation could support multiple different organizations (Internet Facing Deployments and CRM online have something similar but use different server names instead of the virtual directory so that cookies can be kept separate).

However, it can lead to some confusion when wanting to link to CRM files.  Say you put a .html file in an iframe – do you use orgname or not?

The /ORGNAME/ handling is done via an ASP.NET virtual path provider – which means that it only kicks in if the ASP.NET engine is processing the request.  By default, IIS is normally configured to process requests for URLs ending in .aspx via a “Handler Mapping” .aspx to the ASP.NET engine, but resources like .html or .js or .gif or .jpg usually get processed by the default Static File handler.

So a .html file under http://[crm server]/ORGNAME/isv/myapp/myapp.html will 404, but the same file as a .aspx would happily show up.  To get to the static file, you need to leave out the /ORGNAME/: http://[crm server]/isv/myapp/myapp.html

Note that if you do want to link to a .aspx, the global JavaScript function prependOrgName is the best way to add the org name:

prependOrgName(‘/isv/myapp/myapp.aspx’)


Posted by David Eison on Wednesday, September 1, 2010 3:32 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Elmah and SoapException

A web app I’m working on which uses web service calls a lot is using Elmah for error logging.

Unfortunately web service calls consistently result in the useless ‘Server was unable to process request.’ message.

Originally I thought I could patch Elmah to log session variables then log the detail in the session in a Global.asax application_error handler.  Unfortunately, the Elmah error handler appears to consistently run before the application_error handler, so that doesn’t work (although it’s still nice to have session variables logged).

So I did some quick hacking on Elmah to get it to pull out the useful SoapException.Detail message. 

In Elmah’s Error.cs file, after:

if (httpException != null)
{
    _statusCode = httpException.GetHttpCode();
    _webHostHtmlMessage = Mask.NullString(httpException.GetHtmlErrorMessage());
}

I added:

// If this is a SOAP exception, then replace the message information with more useful info
System.Web.Services.Protocols.SoapException soapException = baseException as System.Web.Services.Protocols.SoapException;
if (soapException != null && soapException.Detail != null)
{
    _detail = baseException.Message 
        + System.Environment.NewLine + System.Environment.NewLine 
        + "Soap Detail: " + Mask.NullString(soapException.Detail.InnerText) 
        + System.Environment.NewLine + System.Environment.NewLine 
        + "Regular detail: " + System.Environment.NewLine + _detail;
    _message = Mask.NullString(soapException.Detail.InnerText);
    // not sure how big these detail strings get, so arbitrarily cap the size on the short message
    if (_message.Length > 100)
    {
        _message = _message.Substring(0, 100);
    }
}

And now I get Elmah error logs like:

elmah_log_new

Instead of:

elmah_log_old

And detail messages like:

elmah_log_newdetail

Instead of:

elmah_log_olddetail


Posted by David Eison on Thursday, August 19, 2010 2:33 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Obscure linq to sql issue - don't put 'using' at the of your partial class

Just got burned by this: http://connect.microsoft.com/VisualStudio/feedback/details/361577/mslinqtosqlgenerator-fails-with-partial-class-having-a-using-at-the-top-of-the-file

Symptom:

Linq breaks and exiting/restarting doesn’t fix it.  Your database project is now missing a ‘.designer.cs’ file under your .dbml file.

Cause:

If you put a ‘using’ statement at the top of a partial class for a linq to sql database file (we routinely recommend making these partial classes to replace the default constructor, e.g. http://blog.arkesystems.com/post/2008/03/Using-the-connection-strings-in-your-webconfig-for-LINQ.aspx ) then sometimes Visual Studio deletes your .designer.cs file and so your linq just quits compiling.

Workaround is to move the using statement inside the namespace declaration, then restore your now-deleted .designer.cs file from version control or backups or else right click on the .dbml file and select "Run Custom Tool" to rebuild the file.

Timebomb, one day might kaboom:

using System.Configuration;
namespace Symmedian.SST.Data
{
    partial class MyLinqDataContext {

Perfectly happy and fine:

namespace Symmedian.SST.Data
{
    using System.Configuration;
    partial class MyLinqDataContext {


Categories: ASP.NET | LINQ
Posted by David Eison on Sunday, August 8, 2010 9:46 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Microsoft Dynamics CRM 4.0 – Guids ToString – Even simple things can be complicated

One common need when working with CRM is to convert GUIDs to String.  Say, you want to build up some fetchxml.  No problem, .ToString(), right?

Well, Guids can actually be converted to strings in many different ways – even just the hexadecimal string can be with brackets, without brackets, uppercase, lowercase, with or without hyphens, etc. 

To keep your code as reliable and readable as possible, you’ll want to do this consistently – CRM uses uppercase hexadecimal, with brackets.  Guid has a ToString that takes a format string that you can pass it several different options.  “B” is closest to what we want, but not quite right, because it returns lowercase.  So you should convert to uppercase… but you’ll want to be careful, because converting to uppercase behavior can vary depending on the locale of the box running the code.  Usually your code will run on a server and it won’t matter, but what if you are writing a plugin which is running offline on an outlook client?  I don’t actually know if there is a locale where the hexadecimal characters A-F don’t convert right, but I know that in Turkey the uppercase of i is not I, so I don’t want to push my luck that no culture does something similar with A-F (update: Writing culture-safe managed code says Turkish and Azeri are the only languages with single character case differences, but this points out there may be multiple character case differences, such as Ff in welsh, and then there are some languages that don’t have some letters, such as Cryllic has no C for example…).  So, to be as safe as possible, specify the ‘InvariantCulture’ for any string comparisons or conversions that should deal with strings in a program-readable consistent manner.

Finally, to make this easily accessible to all of your code, you can add a utility method directly to the Guid class as an Extension Method

using System;
using System.Globalization;

namespace arkesystems.crm
{
    public static class CRMUtilExtensionmethods
    {
        public static string ToStringForCRM(this Guid guid)
        {
            return guid.ToString("B", CultureInfo.InvariantCulture).ToUpper(CultureInfo.InvariantCulture);
        }
    }
}

 

Now whenever I want to turn a guid into a string, I just have to have

using arkesystems.crm;

at the top of the class, and my extension method is available directly on the Guid itself (handily also in the autocomplete dropdown):

Guid taskGuid = crmservice.Create(t);
string strTaskGuid = taskGuid.ToStringForCRM();

Posted by David Eison on Friday, August 6, 2010 6:44 PM
Permalink | Comments (0) | Post RSSRSS comment feed