Blazor Webassembly: How to insert components into a string - localization

I have this component that displays generic messages:
<span>#message</span>
The messages are identified by an id and come from string tables in resources files (multiple languages). An example of a message would be:
"Hello {user}! Welcome to {site}!"
So in the basic case, I simply parse the string and replace {user} with, say, "John Doe" and {site} with "MySiteName". The result is set to message and is then properly (and safely) rendered.
But what I would like to do is actually replace {site} with a component that I created that displays the site name with special font and styling. I also have other cases where I want to replace special {markings} with components.
How would you approach this problem ? Is there a way to "insert" a component into a string and then insert the string "safely" to be rendered ? I say "safely" because portions of the final string may come from the DB and be inherently unsafe (like user's name) so inserting the string with something like #((MarkupString)message) does not seem safe.
EDIT:
Thanks to MrC aka Shaun Curtis from whom this final solution is greatly inspired. I marked his answer as the best one.
So I finally went with a scoped service that gets the strings from the resources files, parse them and return a list of RenderFragments that it gets from a component's static table. I use dynamic objects to send specific parameters to the RenderFragments when required.
I basically now get all the text of my app through this centralized mechanism.
Here is an example of an entry in a resource file string table:
Name: "welcome"; Value: "Welcome to {site:name} {0}!"
Here is how it is used in a component:
<h3><Localizer Key="notif:welcome" Data="#(new List<string>() { NotifModel.UserNames.First })"/></h3>
You can see the simplified component and service code below. I explicitely left out the validation and error checking code for simplicity.
#using MySite.Client.Services.Localizer
#inject ILocalizerService Loc
#foreach (var fragment in _fragments)
{
#fragment.Renderer(fragment.Item)
}
#code
{
private List<ILocalizerService.Fragment> _fragments;
public enum RendererTypes
{
Default,
SiteName,
SiteLink,
}
public static Dictionary<RendererTypes, RenderFragment<dynamic>> Renderers = new Dictionary<RendererTypes, RenderFragment<dynamic>>()
{
// NOTE: For each of the following items, do NOT insert a space between the end of the markup and the closing curly brace otherwise it will be rendered !!!
// Like here ↓↓
{ RendererTypes.Default, (model) => #<span>#(model as string)</span>},
{ RendererTypes.SiteName, (model) => #<MySiteNameComponent />},
{ RendererTypes.SiteLink, (model) => ##model.LinkTxt}
};
[Parameter]
public string Key { get; set; }
[Parameter]
public List<string> Data { get; set; }
protected override void OnParametersSet()
{
_fragments = Loc.GetFragments(Key, Data);
}
}
interface ILocalizerService
{
public struct Fragment
{
public Fragment(RenderFragment<dynamic> renderer)
: this(renderer, default)
{
}
public Fragment(RenderFragment<dynamic> renderer, dynamic item)
{
Renderer = renderer;
Item = item;
}
public RenderFragment<dynamic> Renderer { get; set; }
public dynamic Item { get; set; }
}
List<Fragment> GetFragments(string key, List<string> parameters);
}
internal sealed class LocalizerService : ILocalizerService
{
private readonly Dictionary<string, IStringLocalizer> _strLoc = new Dictionary<string, IStringLocalizer>();
public LocalizerService(IStringLocalizer<MySite.Shared.Resources.App> appLoc,
IStringLocalizer<MySite.Shared.Resources.Connection> connLoc,
IStringLocalizer<MySite.Shared.Resources.Notifications> notifLoc)
{
// Keep string localizers
_strLoc.Add("app", appLoc);
_strLoc.Add("conn", connLoc);
_strLoc.Add("notif", notifLoc);
}
public List<Fragment> GetFragments(string key, List<string> parameters)
{
var list = new List<Fragment>();
GetFragments(list, key, parameters);
return list;
}
private void GetFragments(List<Fragment> list, string key, List<string> parameters)
{
// First, get key tokens
var tokens = key.Split(':');
// Analyze first token
switch (tokens[0])
{
case "site":
// Format : {site:...}
ProcessSite(list, tokens, parameters);
break;
default:
// Format : {0|1|2|...}
if (uint.TryParse(tokens[0], out var paramIndex))
{
ProcessParam(list, paramIndex, parameters);
}
// Format : {app|conn|notif|...}
else if (_strLoc.ContainsKey(tokens[0]))
{
ProcessStringLocalizer(list, tokens, parameters);
}
break;
}
}
private void ProcessSite(List<Fragment> list, string[] tokens, List<string> parameters)
{
// Analyze second token
switch (tokens[1])
{
case "name":
// Format {site:name}
// Add name component
list.Add(new Fragment(Shared.Localizer.Renderers[Shared.Localizer.RendererTypes.SiteName]));
break;
case "link":
// Format {site:link:...}
ProcessLink(list, tokens, parameters);
break;
}
}
private void ProcessLink(List<Fragment> list, string[] tokens, List<string> parameters)
{
// Analyze third token
switch (tokens[2])
{
case "user":
// Format: {site:link:user:...}
ProcessLinkUser(list, tokens, parameters);
break;
}
}
private void ProcessLinkUser(List<Fragment> list, string[] tokens, List<string> parameters)
{
// Check length
var length = tokens.Length;
if (length >= 4)
{
string linkUrl;
string linkTxt;
// URL
// Format: {site:link:user:0|1|2|...}
// Retrieve handle from param
if (!uint.TryParse(tokens[3], out var paramIndex))
{
throw new ApplicationException("Invalid token!");
}
var userHandle = GetParam(paramIndex, parameters);
linkUrl = $"/user/{userHandle}";
// Text
if (length >= 5)
{
if (tokens[4].Equals("t"))
{
// Format: {site:link:user:0|1|2|...:t}
// Use token directly as text
linkTxt = tokens[4];
}
else if (uint.TryParse(tokens[4], out paramIndex))
{
// Format: {site:link:user:0|1|2|...:0|1|2|...}
// Use specified param as text
linkTxt = GetParam(paramIndex, parameters);
}
}
else
{
// Format: {site:link:user:0|1|2|...}
// Use handle as text
linkTxt = userHandle;
}
// Add link component
list.Add(new Fragment(Shared.Localizer.Renderers[Shared.Localizer.RendererTypes.SiteLink], new { LinkUrl = linkUrl, LinkTxt = linkTxt }));
}
}
private void ProcessParam(List<Fragment> list, uint paramIndex, List<string> parameters)
{
// Add text component
list.Add(new Fragment(Shared.Localizer.Renderers[Shared.Localizer.RendererTypes.Default], GetParam(paramIndex, parameters)));
}
private string GetParam(uint paramIndex, List<string> parameters)
{
// Proceed
if (paramIndex < parameters.Length)
{
return parameters[paramIndex];
}
}
private void ProcessStringLocalizer(List<Fragment> list, string[] tokens, List<string> parameters)
{
// Format {loc:str}
// Retrieve string localizer
var strLoc = _strLoc[tokens[0]];
// Retrieve string
var str = strLoc[tokens[1]].Value;
// Split the string in parts to see if it needs formatting
// NOTE: str is in the form "...xxx {key0} yyy {key1} zzz...".
// This means that once split, the keys are always at odd indexes (even if {key} starts or ends the string)
var strParts = str.Split('{', '}');
for (int i = 0; i < strParts.Length; i += 2)
{
// Get parts
var evenPart = strParts[i];
var oddPart = ((i + 1) < strParts.Length) ? strParts[i + 1] : null;
// Even parts are always regular text. If not null or empty, we add directly
if (!string.IsNullOrEmpty(evenPart))
{
list.Add(new Fragment(Shared.Localizer.Renderers[Shared.Localizer.RendererTypes.Default], evenPart));
}
// Odd parts are always keys. If not null or empty, get fragments recursively
if (!string.IsNullOrEmpty(oddPart))
{
GetFragments(list, oddPart, parameters);
}
}
}
}

You don't necessarily need to build components. A component is a c# class that emits a RenderFragment.
You could simply build RenderFragments for {site},... Here's a simple static class that shows two ways to do this:
namespace StackOverflowAnswers;
public static class RenderFragements
{
public static RenderFragment SiteName => (builder) =>
{
// Get the content from a service that's accessing a database and checking the culture info for language
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", "p-2 bg-primary text-white");
builder.AddContent(2, "My Site");
builder.CloseElement();
};
public static RenderFragment GetSiteName(string sitename) => (builder) =>
{
// parse to make sure you're happy with the string
builder.OpenElement(0, "span");
builder.AddAttribute(1, "class", "p-2 bg-dark text-white");
builder.AddContent(2, sitename);
builder.CloseElement();
};
}
And here's an index page using them:
#page "/"
#using StackOverflowAnswers
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
<div class=m-2>
The site name for this site is #(RenderFragements.GetSiteName("this site"))
</div>
#(RenderFragements.SiteName)
With the RenderFragment your writing c# code. You can run a parser to check the string before rendering it.
You could have a scoped service that gets the info from the database for the user and exposes a set of RenderFragments you then use in your pages/components.

I used regex to split the source at the tokens configured in TokenMappings. Token mappings could easily be loaded from a json source for example. To configure more "{markings}" just add more lines to the TokenMappings.
<StringParser Source="Hello {user}! Welcome to {site}!" />
StringParser.razor
#foreach (var subString in substrings)
{
if (tokens.Contains(subString))
{
var key = StripCurlyBrackets(subString);
<DynamicComponent Type=#(TokenMappings[key].Item1)
Parameters=#(TokenMappings[key].Item2) />
}
else
{
#subString
}
}
#code {
private Dictionary<string, (Type, Dictionary<string, object>?)> TokenMappings;
private string[] substrings;
private string[] tokens;
[Parameter]
public string Source { get; set; }
protected override void OnParametersSet()
{
var user = "John Doe"; // I would expect these are supplied via a signin context.
var site = "MySiteName"; //
TokenMappings = new Dictionary<string, (Type, Dictionary<string, object>?)>
{
{ "user", ( typeof(UserComponent), new Dictionary<string, object>{ { "User", user } } ) },
{ "site", ( typeof(SiteComponent), new Dictionary<string, object>{ { "Site", site } } ) }
};
var keys = TokenMappings.Keys.Select(a => a);
var pattern = keys.Select(key => $"({{(?:{key})}})").Aggregate((a, b) => a + "|" + b);
this.substrings = System.Text.RegularExpressions.Regex.Split(Source, pattern);
this.tokens = TokenMappings!.Keys.Select(key => $"{{{key}}}").ToArray();
base.OnParametersSet();
}
private string StripCurlyBrackets(string source)
{
return source
.Replace(oldValue: "{", newValue: string.Empty)
.Replace(oldValue: "}", newValue: string.Empty);
}
}
Yes MarkupString allows you to render html.
substrings :

Related

ApplicationUser is not saving changes to field after there is already a value in that field

I'm working on a project that keeps track of the hours employees have worked between different crews. The way the app is supposed to work is that an employee submits a Daysheet form that has their clock-in and clock-out time and the id of their Field Manager(FMId). Each Field Manager has a column in the database that should keep track of the DaysheetIds of forms that are submitted with their FMId. The problem is that this column is only saving the first DaysheetId that gets submitted. Once there's a value in that column, the Field Manager doesn't update to add any other DaysheetIds.
To keep my post concise, I'm trying to post only the relevant code, but let me know if I'm missing something that would be important.
Here's the action in my controller where the Field Manager should be updated. I added the var FM just before the return to see what was happening to the Field Manager with the debugger.
public IActionResult Submit(EmployeeDaySheetViewModel employeeDaySheetViewModel)
{
if (ModelState.IsValid)
{
var newDaySheet = new EmployeeDaySheet
{
Id = employeeDaySheetViewModel.DaysheetId,
EmployeeId = employeeDaySheetViewModel.EmployeeId,
EmployeeName = employeeDaySheetViewModel.EmployeeName,
FMId = employeeDaySheetViewModel.FMId,
Date = employeeDaySheetViewModel.Date,
ClockIn = employeeDaySheetViewModel.ClockIn,
ClockOut = employeeDaySheetViewModel.ClockOut,
};
var success = _employeeDaySheetRepository.AddDaySheet(newDaySheet);
if (success)
{
_applicationUserRepository
.AddCrewDaySheetToFieldManager(employeeDaySheetViewModel.FMId,
employeeDaySheetViewModel.DaysheetId);
TempData["UserMessage"] = "Successfully submitted daysheet for " + DateTime.Now.Day.ToString();
}
else
{
TempData["ErrorMessage"] = "Unable to submit daysheet. Please try again in a few minutes.";
}
}
var FM = _applicationUserRepository.GetFieldManagerById(employeeDaySheetViewModel.FMId);
return Redirect("/home/");
Here is the AddCrewDaySheetToFieldManager method in my user repository that's being called in the controller action:
public bool AddCrewDaySheetToFieldManager(string FMId, string DaySheetId)
{
var fieldManager = _applicationDbContext.ApplicationUsers
.FirstOrDefault(u => u.Id == FMId);
if (fieldManager.CrewDaySheetIds != null)
{
var oldCrewIds = fieldManager.CrewDaySheetIds;
// If the user deletes all their crewIds, the database leaves an empty string in their crewIds column.
// Replacing user.crewIds that has "" at index 0 is the same as starting a new list.
if (oldCrewIds[0] == "")
{
var newCrewIds = new List<string> { DaySheetId };
fieldManager.CrewDaySheetIds = newCrewIds;
}
else
{
fieldManager.CrewDaySheetIds.Add(DaySheetId);
}
}
else { fieldManager.CrewDaySheetIds = new List<string> { DaySheetId }; }
_applicationDbContext.SaveChanges();
return true;
This is what my ApplicationUser looks like:
public class ApplicationUser : IdentityUser
{
public string Tier { get; set; }
public bool IsFieldManager { get; set; }
public double HourRate { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public List<string> PayrollObjectIds { get; set; }
public List<string> CrewDaySheetIds { get; set; }
}
In order to convert the lists attributes of this object to strings, I've got this in my DbContext
protected override void OnModelCreating(ModelBuilder builder)
{
var splitStringConverter = new ValueConverter<List<string>, string>(v => string.Join(";", v), v => v.Split(new[] { ';' }).ToList());
builder.Entity<ApplicationUser>().Property(nameof(ApplicationUser.PayrollObjectIds)).HasConversion(splitStringConverter);
builder.Entity<ApplicationUser>().Property(nameof(ApplicationUser.CrewDaySheetIds)).HasConversion(splitStringConverter);
base.OnModelCreating(builder);
//I also seed some data here//
}
What I can't figure out is the point at which the data is not getting saved. When I run the debugger, I can see the fieldManager is getting updated. In the AddCrewDaySheetToFieldManager method, fieldManager.CrewDaySheetIds has the DaysheetId. Then, back in the action, just before return Redirect('/home/')the FM.CrewDaySheetIds still has the DaysheetId. However, when I look at the database or try to access the CrewDaysheetIds on the FM user, only the first DaysheetId is there.
I suspect that there's something going wrong with the splitStringConversion, but I've used the same code in another project and not had this issue, so I'm stuck for what to do.
I didn't understand your question correctly but if you want update or insert some data in your database you can use _applicationDbContext.ApplicationUsers.add(fieldManager); for insert, and _applicationDbContext.Entry(fieldManager).State = EntityState.Modeified; for update. but you didn't use any of them before _applicationDbContext.SaveChanges().
I think you have to write this:
public bool AddCrewDaySheetToFieldManager(string FMId, string DaySheetId)
{
var fieldManager = _applicationDbContext.ApplicationUsers
.FirstOrDefault(u => u.Id == FMId);
if (fieldManager.CrewDaySheetIds != null)
{
var oldCrewIds = fieldManager.CrewDaySheetIds;
// If the user deletes all their crewIds, the database leaves an empty string in their crewIds column.
// Replacing user.crewIds that has "" at index 0 is the same as starting a new list.
if (oldCrewIds[0] == "")
{
var newCrewIds = new List<string> { DaySheetId };
fieldManager.CrewDaySheetIds = newCrewIds;
}
else
{
fieldManager.CrewDaySheetIds.Add(DaySheetId);
}
}
else { fieldManager.CrewDaySheetIds = new List<string> { DaySheetId }; }
_applicationDbContext.entry(fieldManager).State = EntityState.Modified;
_applicationDbContext.SaveChanges();
return true;

uCommerce - add dynamic property to order line

I have hit a problem building a uCommerce site based on top of the demo razor store available http://thesitedoctor.co.uk/portfolio/avenue-clothingcom/
The demo uses servicestack and the ucommerceapi for its basket functions.
I am trying to add a dynamic property to the basket (on an order line) at the point where the user clicks buy. I traced through the productpage.js file and amended the code to add a new property ('message'):
function (data) {
var variant = data.Variant;
$.uCommerce.addToBasket(
{
sku: variant.Sku,
variantSku: variant.VariantSku,
quantity: qty,
message: $('#personalisedMessage').val()
},
function () {
updateCartTotals(addToCartButton);
}
);
});
using firebug, i checked the data that is being posted
addToExistingLine: true
message: "this is a message"
quantity:"1"
sku: "Product (options: none)"
variantSku:""
Posting this does not cause an error, but I cannot tell if it has worked either - I cannot find it in the database, assuming that it would be stored in OrderProperty table. In this scenario, I am 'buying' a product with no variations.
Any help is greatly appreciated with this.
Out of the box you can't add order/line item properties via the API like that. The API payload that you've added to is specified although valid JSON won't get interpreted/used by the API.
Instead what you'll need to do is add your own method to the API. To do this you'll need to implement a service from IUCommerceApiService and then you can do what you need. I've created an example (untested) below and will get it added to the demo store as I think it's a useful bit of functionality to have.
public class AddOrderLineProperty
{
public int? OrderLineId { get; set; }
public string Sku { get; set; }
public string VariantSku { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}
public class AddOrderLinePropertyResponse : IHasResponseStatus
{
public AddOrderLinePropertyResponse() { }
public AddOrderLinePropertyResponse(UCommerce.EntitiesV2.OrderLine line)
{
if (line == null)
{
UpdatedLine = new LineItem();
return;
}
var currency = SiteContext.Current.CatalogContext.CurrentCatalog.PriceGroup.Currency;
var lineTotal = new Money(line.Total.Value, currency);
UpdatedLine = new LineItem()
{
OrderLineId = line.OrderLineId,
Quantity = line.Quantity,
Sku = line.Sku,
VariantSku = line.VariantSku,
Price = line.Price,
ProductName = line.ProductName,
Total = line.Total,
FormattedTotal = lineTotal.ToString(),
UnitDiscount = line.UnitDiscount,
VAT = line.VAT,
VATRate = line.VATRate
};
}
public ResponseStatus ResponseStatus { get; set; }
public LineItem UpdatedLine { get; set; }
}
public class AddOrderLinePropertyService : ServiceBase<AddOrderLineProperty>, IUCommerceApiService
{
protected override object Run(AddOrderLineProperty request)
{
var orderLineId = request.OrderLineId;
var sku = request.Sku;
var variantSku = request.VariantSku;
var orderLine = findOrderLine(orderLineId, sku, variantSku);
addPropertyToOrderLine(orderLine, request.Key, request.Value);
TransactionLibrary.ExecuteBasketPipeline();
var newLine = findOrderLine(orderLineId, sku, variantSku);
return new AddOrderLinePropertyResponse(newLine);
}
private void addPropertyToOrderLine(OrderLine orderLine, string key, string value)
{
if (orderLine == null)
return;
orderLine[key] = value;
orderLine.Save();
}
private static OrderLine findOrderLine(int? orderLineId, string sku, string variantSku)
{
return orderLineId.HasValue
? getOrderLineByOrderLineId(orderLineId)
: getOrderLineBySku(sku, variantSku);
}
private static OrderLine getOrderLineBySku(string sku, string variantSku)
{
return String.IsNullOrWhiteSpace(variantSku)
? getOrderLines().FirstOrDefault(l => (l.Sku == sku))
: getOrderLines().FirstOrDefault(l => (l.Sku == sku && l.VariantSku == variantSku));
}
private static OrderLine getOrderLineByOrderLineId(int? orderLineId)
{
return getOrderLines().FirstOrDefault(l => l.OrderLineId == orderLineId);
}
private static ICollection<OrderLine> getOrderLines()
{
return TransactionLibrary.GetBasket().PurchaseOrder.OrderLines;
}
}
You'll need to add the new method to uCommerce.jQuery.js as well something like this:
addOrderLineProperty: function (options, onSuccess, onError) {
var defaults = {
orderLineId: 0
};
var extendedOptions = $.extend(defaults, options);
callServiceStack({ AddOrderLineProperty: extendedOptions }, onSuccess, onError);
}
Let me know if you have any issues using it.
Tim

Return multiple objects using Json.Net

With the built-in json converter I return multiple objects in my action like this:
return Json(new { success = true, data = units });
When I use the JSON.NET library how can I do the same?
This does obviously not compile:
return new { success = true, data = JsonConvert.SerializeObject(units) };
I do not want to create an extra viewmodel for this containing both properties.
Do I have a wrong understanding of the default Json javascript serializer maybe ?
If you want to use Newtonsoft.Json to serialise your objects, you can create a new ActionResult class and pass the data in the result.
For example:
public class NewtonsoftJsonResult : ContentResult
{
private readonly object _data;
public NewtonsoftJsonResult(object data)
{
_data = data;
}
public override void ExecuteResult(ControllerContext context)
{
Content = JsonConvert.SerializeObject(_data);
ContentType = "application/json";
base.ExecuteResult(context);
}
}
Just return your custom ActionResult with the anonymous object as data:
public ActionResult Index()
{
return new NewtonsoftJsonResult(new { success = true, data = units});
}
In your second example, JsonConvert.SerializeObject(units) will result in a string returned to JavaScript. JavaScript won't see data as containing some "real" data but rather a simple string, with curly parentheses inside.
Use your first sentence as usual. MVC's Json method will serialize the objects within.
For example:
class Units
{
public int Width { get; set; }
public int Height { get; set; }
}
...
Units u = new Units { Width = 34, Height = 20 };
return Json(new { success = true, data = units });
will result in a Json that looks similar to this:
{ "success" : "true", "data" : { "Height" : "20", "Width" : "34" } } }

How do I send deep JSON objects to an Action?

In my web app I have a dynamically generated form that I use to create a JSON object to pitch back to an Action. As seen here:
function getConfigItemWithValidators() {
log.info("getConfigItemWithValidators()");
var oConfigItem = {
"Name": $("#txtName").html(),
"InputFileCellIndex": $("#inpFieldIndex").val(),
"Validators": new Array() };
for (var i = 0; true; i++) {
var oHiddenValidatorName = $("[name=hidVld"+i+"]");
var oHiddenValidatorVal = $("[name=txtVld"+i+"]");
if ($("[name=hidVld" + i + "]").length > 0) {
var oValidator = {
"ValidationType": oHiddenValidatorName.val(),
"ValidationValue": oHiddenValidatorVal.val() };
oConfigItem.Validators.push(oValidator);
}
else
break;
}
return oConfigItem
}
function saveConfigItemChanges() {
log.info("saveConfigItemChanges()");
var oConfigItem = getConfigItemWithValidators();
$("#divRulesContainer").hide("normal");
$.getJSON("PutValidationRules", oConfigItem,
saveConfigItemChangesCallback);
}
In my action, while debugging, I notice that model.Validators is empty:
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult PutValidationRules(ConfigItem model)
{
// model.Validators is empty
return Json(true);
}
Here is the code to ConfigItem:
public class ConfigItem
{
public string Name { get; set; }
public int InputFileCellIndex { get; set; }
private IList<Validator> _validators = new List<Validator>();
public IList<Validator> Validators
{
get
{
return _validators;
}
}
public void AddValidator(Validator aValidator)
{
aValidator.ConfigItem = this;
_validators.Add(aValidator);
}
}
Is there something I need to do to get ConfigItem.Validators to get built for my JSON requests?
It is empty because default binder does not work for arrays very well. You will need to implement a custombinder.
You can see here an example of custombinders

How to get list of customers, jobs, and employeers using Quickbooks QBFC (8.0 SDK)

I have been given the painful task of writing a C# application to sync up employee time entries in a separate database with Quickbooks. Since I'm brand new to QB programming, I'm trying to peform basic tasks, such as getting a list of customers, then jobs for each customer, then employees. I've been reading the SDK documentation, but I'm still a little fuzzy on the details because the examples I'm finding are a little too advanced for me at the moment :-P
To keep things simple, I would like to ask for a code snippet that gives me the list of customers for starters. Here's the code I've got:
QBSessionManager SessionManager = new QBSessionManager();
IMsgSetRequest customerSet = SessionManager.CreateMsgSetRequest("US", 8, 0);
//
// Code to get list of customers here.
//
SessionManager.OpenConnection2("", "New App", ENConnectionType.ctLocalQBD);
SessionManager.BeginSession(string.Empty, ENOpenMode.omDontCare);
IMsgSetResponse Resp = SessionManager.DoRequests(customerSet);
MessageBox.Show(Resp.ToXMLString());
SessionManager.EndSession();
SessionManager.CloseConnection();
Can anyone fill in the "code to get list of customers here" for me? Thank you very much in advance!
Victor
customers.IncludeRetElementList.Add("IsActive");
customers.IncludeRetElementList.Add("ListID");
customers.IncludeRetElementList.Add("EditSequence");
customers.IncludeRetElementList.Add("Name");
customers.IncludeRetElementList.Add("ParentRef");
Only the fields specified in the above list will be returned from QuickBooks - it is very important to use the correct strings in the correct case - no error messages will result if something is wrong. You cannot specify sub-fields (eg, City within an Address block; you must get the entire Address block). For custom fields, you also must specify the OwnerID (use 0 for custom fields that are not private to an application)
customers.IncludeRetElementList.Add("DataExtRet"); //will return non-private and/or private data extension fields depending on the OwnerIDList, below
customers.OwnerIDList.Add("0"); // required for non-private data extn fields
customers.OwnerIDList.Add("Your Appln GUID"); // Use this to get private data extns for the Appln identified by the GUID
Ok, seems like I found the missing piece:
ICustomerQuery customers = customerSet.AppendCustomerQueryRq();
This produces all the data related to each customer, which is a step forward. Parsing the XML for customers should be pretty straightforward, but parsing the individual tasks/jobs for each customer will be laborious, because there are no subnodes for each task - basically you get repeating chunks of XML with all the basic customer info (address, billing address, shipping address, etc.), then this one property called "FullName" which appends a colon to the customer name, followed by the task title (which itself can be followed by another colon with a subtask title, etc.). I'm wondering if there's something clever I can do with the request query to get a better xml response (for instance, specify what properties I want returned, and maybe enforce the creation of subnodes for each task for a given customer)...comments are appreciated.
Adding to Victors, Chili and Hassan's answer. Glad to have stumbled on this question as I myself was struggling with the QBFC examples.
Here is a full set of code that might just help someone down the road. If in the meantime someone could just point me in the direction of some useful documentation...that would be great.
Getting the Employee data to an XML string
public static string EmployeeListXML()
{
QBSessionManager SessionManager = new QBSessionManager();
IMsgSetRequest msgSetReq = SessionManager.CreateMsgSetRequest("US", 8, 0);
IEmployeeQuery employee = msgSetReq.AppendEmployeeQueryRq();
employee.IncludeRetElementList.Add("IsActive");
employee.IncludeRetElementList.Add("ListID");
employee.IncludeRetElementList.Add("EditSequence");
employee.IncludeRetElementList.Add("FirstName");
employee.IncludeRetElementList.Add("LastName");
employee.IncludeRetElementList.Add("SSN");
//employee.IncludeRetElementList.Add("ParentRef");
//employee.IncludeRetElementList.Add("DataExtRet"); //will return non-private and/or private data extension fields depending on the OwnerIDList, below
employee.OwnerIDList.Add("0"); // required for non-private data extn fields
//customers.OwnerIDList.Add("Your Appln GUID"); // Use this to get private data extns for the Appln identified by the GUID
SessionManager.OpenConnection2("", Application.ProductName, ENConnectionType.ctLocalQBD);
//SessionManager.BeginSession(string.Empty, ENOpenMode.omDontCare);
SessionManager.BeginSession(frmMain.QBFileName, ENOpenMode.omDontCare); // I have the filename on frmMain
IMsgSetResponse Resp = SessionManager.DoRequests(msgSetReq);
SessionManager.EndSession();
SessionManager.CloseConnection();
//MessageBox.Show(Resp.ToXMLString());
return Resp.ToXMLString();
}
Putting the XML string into a List of Emplpoyee Objects
public static List<Employee> EmployeeXMLtoList()
{
string sXML = EmployeeListXML();
List<Employee> lstEmp = new List<Employee>();
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(sXML);
XmlNodeList parentNode = xmlDoc.GetElementsByTagName("EmployeeRet");
foreach (XmlNode childNode in parentNode)
{
Employee oEmp = new Employee();
oEmp.ListID = childNode.SelectSingleNode("ListID").InnerText;
oEmp.EditSequence = childNode.SelectSingleNode("EditSequence").InnerText;
oEmp.Active = childNode.SelectSingleNode("IsActive").InnerText;
oEmp.FirstName = childNode.SelectSingleNode("FirstName").InnerText;
oEmp.LastName = childNode.SelectSingleNode("LastName").InnerText;
oEmp.SSN = childNode.SelectSingleNode("SSN").InnerText;
lstEmp.Add(oEmp);
}
return lstEmp;
}
Function that return an Employee Object using Linq
public static Employee GetEmployeeObject(string sSSN)
{
Employee oReturn = null;
List<Employee> lstEmployee = EmployeeXMLtoList();
IEnumerable<Employee> lstEmps = from oEmp in lstEmployee
where oEmp.SSN == sSSN
select oEmp;
foreach (var oEmp in lstEmps)
{
oReturn = oEmp;
}
return oReturn;
}
Example of Code
Employee oEmployee = QB.GetEmployeeObject("112-35-8560");
Employee Class
public class Employee
{
private string sEmployeeID;
private string sSSN;
private string sLastName;
private string sFirstName;
private string sAddress1;
private string sAddress2;
private string sCity;
private string sState;
private string sZipCode;
private string sGender;
private string sEthnicity;
private DateTime dDOB;
private string sMaritalStatus;
private int iDependants;
private string sUScitizen;
private decimal iPayRate;
private string sPhone;
private DateTime dHireDate;
private string sEmail;
public Employee() { }
public string EmployeeID
{
get { return sEmployeeID; }
set { sEmployeeID = value; }
}
public string ListID
{
get; set;
}
public string EditSequence
{
get; set;
}
public string Active
{
get; set;
}
public string SSN
{
get { return sSSN; }
set { sSSN = value; }
}
public string LastName
{
get { return sLastName; }
set { sLastName = value; }
}
public string FirstName
{
get { return sFirstName; }
set { sFirstName = value; }
}
public string FullName
{
get { return FirstName + " " + LastName; }
set { }
}
public string Address1
{
get { return sAddress1; }
set { sAddress1 = value; }
}
public string Address2
{
get { return sAddress2; }
set { sAddress2 = value; }
}
public string State
{
get { return sState; }
set { sState = value; }
}
public string City
{
get { return sCity; }
set { sCity = value; }
}
public string ZipCode
{
get { return sZipCode; }
set { sZipCode = value; }
}
public string Gender
{
get { return sGender; }
set { sGender = value; }
}
public string Ethnicity
{
get { return sEthnicity; }
set { sEthnicity = value; }
}
public DateTime DOB
{
get { return dDOB; }
set { dDOB = value; }
}
public string MaritalStatus
{
get { return sMaritalStatus; }
set { sMaritalStatus = value; }
}
public int Dependants
{
get { return iDependants; }
set { iDependants = value; }
}
public string UScitizen
{
get { return sUScitizen; }
set { sUScitizen = value; }
}
public decimal PayRate
{
get { return iPayRate; }
set { iPayRate = value; }
}
public DateTime HireDate
{
get { return dHireDate; }
set { dHireDate = value; }
}
public string Phone
{
get { return sPhone; }
set { sPhone = value; }
}
public string Email
{
get { return sEmail; }
set { sEmail = value; }
}
}

Resources