ASP.NET Page.ParseControl very slow with Apache/Mono - c#

I'm currently porting an ASP.NET 2.0 application to Apache/Mono (Ubuntu 13.10).
The application uses XSLT to create ASP.NET controls from dynamic content. It does so by using the (infamous) Page.ParseControl(string) method.
Some pages might have >500 dynamic controls. Calling ParseControl on them still used to take only a few milliseconds on IIS/Windows and is only done on initial page-load. For async. post-backs, only a small number of those controls is re-created (like the one that was "clicked") to optimize that stuff.
Now to my problem with Mono:
ParseControl takes between 300-1500 ms for EACH call. Passing those "500 controls" in a loop would probably take forever, so I already optimized it:
Put ALL controls in a wrapper-DIV
Call ParseControl on that
Extract all single elements via C# code
This takes exactly the same time as parsing only 1. The cost of ParseControl seems to be calling it at all I guess. Also it is only that slow, if I pass "new" content to it (so there seems to be some caching already).
ParseControl creates files in /tmp and then starts the ASP.NET-compiler on them as far as I can see. How could I speed this up?
The Mono-sources are full of TODOs refering to that functionallity and it no longer works at all with Ubuntu 14.04 (throws "System.Web.Compilation.CompilationException", see http://mono.1490590.n4.nabble.com/CS1576-after-upgrade-to-Ubuntu-14-04-1-td4663599.html)

Here is my own implementation of ParseControl. It is far from being complete, so don't expect too much.
Results to expect:
Non-server-controls will be put into "LiteralControl"s.
Supported server-controls (runat="server"):
"HtmlControl"s
Controls from "System.Web" assembly
Custom controls defined in "Web.config"
Others (like user controls) should be easy to add (I'm not using them in my project right now).
The result (if it works) will be something similar to what "Page.ParseControl" does, but NOT THE SAME.
Performance:
I use some caching to speed things up, but it will still be ~50% slower on Windows than "Page.ParseControl".
For Mono (tested with 3.12.1) it is actually usable now.
Prerequesites:
Include "HtmlAgilityPack" from http://htmlagilitypack.codeplex.com
Usage:
String html = "<div runat=\"server\" id=\"test123\"></div>";
Control c = null;
try {
// Performance tip: The "doc" object can be cached, if the same HTML needs to be parsed again
var doc = new HtmlDocument();
doc.LoadHtml(html);
c = OwnParseControlEngine.Parse(doc);
}
catch {
c = Page.ParseControl(html); // Note: Will crash with Mono 3.x
}
Code:
/// <summary>
/// Own implementation of "ParseControl". Returns XHTML (default) or HTML.
/// Custom controls from "Web.config" are supported (TODO: User controls and imports on Page are NOT).
/// </summary>
private class OwnParseControlEngine {
public class ParseException : System.Exception {
public ParseException(HtmlNode e)
: base("Unknown ASP.NET server-tag \"" + e.OriginalName + "\".") {
}
}
private static readonly String _systemWebNamespace;
private static readonly String _systemWebAssembly;
private static readonly Dictionary<String, LinkedList<TagPrefixInfo>> _controlsTagPrefixInfos = new Dictionary<String, LinkedList<TagPrefixInfo>>(); // Key is tag-prefix in lowercase
private class Factory {
public delegate Control CreateDel(HtmlNode e);
private readonly CreateDel _del;
public Boolean DropWhiteSpaceLiterals { get; private set; }
public Factory(CreateDel del, Boolean dropWhiteSpaceLiterals = false) {
this._del = del;
this.DropWhiteSpaceLiterals = dropWhiteSpaceLiterals;
}
public Control Create(HtmlNode e) {
return this._del.Invoke(e);
}
}
private static readonly Dictionary<String, Factory> _factories = new Dictionary<String, Factory>(); // Must be locked. Key is tag-name in lowercase.
static OwnParseControlEngine() {
// We cache the results to speed things up. "Panel" is only used to get assembly info.
_systemWebNamespace = typeof(Panel).Namespace;
_systemWebAssembly = typeof(Panel).Assembly.FullName;
var section = (PagesSection)WebConfigurationManager.OpenWebConfiguration("/").GetSection("system.web/pages");
foreach (TagPrefixInfo info in section.Controls) {
LinkedList<TagPrefixInfo> l;
if (!_controlsTagPrefixInfos.TryGetValue(info.TagPrefix, out l)) {
l = new LinkedList<TagPrefixInfo>();
_controlsTagPrefixInfos.Add(info.TagPrefix.ToLower(), l);
}
l.AddLast(info);
}
// Add HTML control types
_factories.Add("span", new Factory((e) => { return new HtmlGenericControl(e.OriginalName); }));
_factories.Add("div", new Factory((e) => { return new HtmlGenericControl(e.OriginalName); }));
_factories.Add("body", new Factory((e) => { return new HtmlGenericControl(e.OriginalName); }));
_factories.Add("font", new Factory((e) => { return new HtmlGenericControl(e.OriginalName); }));
_factories.Add("a", new Factory((e) => { return new HtmlAnchor(); }));
_factories.Add("button", new Factory((e) => { return new HtmlButton(); }));
_factories.Add("form", new Factory((e) => { return new HtmlForm(); }));
_factories.Add("input", new Factory((e) => {
switch (e.Attributes["type"].Value) {
case "button": return new HtmlInputButton();
case "checkbox": return new HtmlInputCheckBox();
case "file": return new HtmlInputFile();
case "hidden": return new HtmlInputHidden();
case "image": return new HtmlInputImage();
case "radio": return new HtmlInputRadioButton();
case "text": return new HtmlInputText();
case "password": return new HtmlInputPassword();
case "reset": return new HtmlInputReset();
case "submit": return new HtmlInputSubmit();
}
throw new ParseException(e);
}));
_factories.Add("select", new Factory((e) => { return new HtmlSelect(); }));
_factories.Add("table", new Factory((e) => { return new HtmlTable(); }, true)); // Adding literals not allowed
_factories.Add("tr", new Factory((e) => { return new HtmlTableRow(); }, true)); // Adding literals not allowed
_factories.Add("td", new Factory((e) => { return new HtmlTableCell(); }));
_factories.Add("textarea", new Factory((e) => { return new HtmlTextArea(); }));
_factories.Add("link", new Factory((e) => { return new HtmlLink(); }));
_factories.Add("meta", new Factory((e) => { return new HtmlMeta(); }));
_factories.Add("title", new Factory((e) => { return new HtmlTitle(); }));
_factories.Add("img", new Factory((e) => { return new HtmlImage(); }));
}
private static void ApplyHtmlControlAttributes(HtmlControl c, HtmlNode e) {
foreach (HtmlAttribute a in e.Attributes) {
if (a.Name == "id")
c.ID = a.Value;
else if (a.Name != "runat")
c.Attributes[a.OriginalName] = HttpUtility.HtmlDecode(a.Value);
}
}
private static void ApplyControlAttributes(Control c, HtmlNode e) {
if (c is WebControl && e.Attributes["style"] != null) {
String style = HttpUtility.HtmlDecode(e.Attributes["style"].Value);
foreach (String s in style.Split(new Char[] { ';' }, StringSplitOptions.RemoveEmptyEntries))
((WebControl)c).Style[s.Substring(0, s.IndexOf(':'))] = s.Substring(s.IndexOf(':') + 1);
}
foreach (PropertyInfo p in c.GetType().GetProperties()) {
if (p.CanRead && p.CanWrite && e.Attributes[p.Name] != null) {
try {
Object v = null;
if (p.PropertyType.IsEnum)
v = Enum.Parse(p.PropertyType, e.Attributes[p.Name].Value);
else if (p.PropertyType == typeof(String))
v = e.Attributes[p.Name].Value;
else if (p.PropertyType == typeof(Boolean))
v = Boolean.Parse(e.Attributes[p.Name].Value);
else if (p.PropertyType == typeof(Int32))
v = Int32.Parse(e.Attributes[p.Name].Value);
else if (p.PropertyType == typeof(Unit))
v = Unit.Parse(e.Attributes[p.Name].Value);
// TODO: More types?
if (v != null)
p.SetValue(c, v, null);
}
catch {
}
}
}
}
private static Control CreateServerControl(HtmlNode e, out Boolean dropWhiteSpaceLiterals) {
Factory cf;
lock (_factories) {
_factories.TryGetValue(e.Name, out cf);
}
if (cf == null) {
Int32 pos = e.Name.IndexOf(':');
if (pos != -1) {
String tagPrefix = e.Name.Substring(0, pos).ToLower();
String name = e.Name.Substring(pos + 1);
Type t = null;
// Try "System.Web" (default assembly)
if (tagPrefix == "asp")
t = Type.GetType(String.Format("{0}.{1}, {2}", _systemWebNamespace, name, _systemWebAssembly), false, true); // "Namespace.ClassName, Assembly"
if (t == null) {
// Try controls specified in "web.config"
LinkedList<TagPrefixInfo> l;
if (_controlsTagPrefixInfos.TryGetValue(tagPrefix, out l)) {
foreach (var info in l) {
// Custom controls
t = Type.GetType(String.Format("{0}.{1}, {2}", info.Namespace, name, info.Assembly), false, true); // "Namespace.ClassName, Assembly"
if (t != null)
break;
// TODO: User controls with tag.TagName, tag.Source
}
}
}
if (t != null) {
cf = new Factory((e2) => { return (Control)Activator.CreateInstance(t); });
lock (_factories) {
_factories[e.Name] = cf; // "Replace" instead of "Add", because another thread might have already added it since the lock above
}
}
}
}
if (cf == null)
throw new ParseException(e);
var c = cf.Create(e);
if (c is HtmlControl)
ApplyHtmlControlAttributes((HtmlControl)c, e);
else
ApplyControlAttributes(c, e);
dropWhiteSpaceLiterals = cf.DropWhiteSpaceLiterals;
return c;
}
private static void ParseChildren(Control parentC, HtmlNode currE, Boolean xhtml = true, Boolean dropWhiteSpaceLiterals = false) {
foreach (HtmlNode childE in currE.ChildNodes) {
Control newC = null, closeTag = null;
Boolean newDropWhiteSpaceLiterals = false;
if (childE.Attributes["runat"] != null && childE.Attributes["runat"].Value.ToLower() == "server") // Server control
newC = CreateServerControl(childE, out newDropWhiteSpaceLiterals);
else { // Literal control
switch (childE.Name) {
case "#text":
if (!dropWhiteSpaceLiterals || childE.InnerText.Trim().Length != 0)
newC = new LiteralControl(childE.InnerText);
break;
default:
String s = String.Format("<{0}", childE.OriginalName);
foreach (HtmlAttribute a in childE.Attributes)
s += String.Format(" {0}=\"{1}\"", a.OriginalName, a.Value);
s += ">";
switch (childE.Name) {
// List of void elements taken from http://www.programmerinterview.com/index.php/html5/void-elements-html5/
case "area": case "base": case "br": case "col": case "command": case "embed": case "hr": case "img": case "input":
case "keygen": case "link": case "meta": case "param": case "source": case "track": case "wbr":
if (xhtml)
s = s.Substring(0, s.Length - 1) + "/>";
newC = new LiteralControl(s);
break;
default:
newC = new PlaceHolder(); // Used as a wrapper to allow child-controls
newC.Controls.Add(new LiteralControl(s));
closeTag = new LiteralControl(String.Format("</{0}>", childE.OriginalName));
break;
}
break;
}
}
if (newC != null) {
parentC.Controls.Add(newC);
ParseChildren(newC, childE, xhtml, newDropWhiteSpaceLiterals);
if (closeTag != null)
newC.Controls.Add(closeTag);
}
}
}
private OwnParseControlEngine() {
}
/// <summary>
/// Parses the given HTML document and returns a Control.
/// Throws "ParseException" on error (TODO: Maybe others too).
/// </summary>
public static Control Parse(HtmlDocument doc) {
var c = new Control();
ParseChildren(c, doc.DocumentNode, false);
return c;
}
}

Related

Twincat Ads Reactive weird Handle behaviour

I'm working with TwincatAds.Reactive 6.0.190 in .NET 6 WPF Desktop application.
I'm also using MVVM pattern.
My goal is to create a Class that is going to observe for a PLC Variable changes, collect those variables to a dictionary, and later on use those values in the ViewModel.
Here's the method where I'm attaching the notification and action where I'm handling the notification.
public void AttachNotification(IEnumerable<(string key, Type type)> Symbols)
{
_observerValueNotification = Observer.Create<ValueNotification>(val =>
{
// Does handle really start from 2?
var handle = val.Handle;
if (val.UserData is object[] objects)
{
string tag = objects[handle - 2].ToString();
if (!_values.Any(x => x.Key == tag))
_values.Add(new SymbolModel { Key = tag, Value = val.Value });
else
{
var symbol = _values.First(x => x.Key == tag);
symbol.Value = val.Value;
}
}
ValuesChanged?.Invoke(_values);
});
if (_plcWrapper.AdsClient != null)
{
// Get Symbols from SymbolLoader
List<AnySymbolSpecifier> list = new();
List<string> userData = new();
foreach (var (key, type) in Symbols)
{
list.Add(new AnySymbolSpecifier(key, new AnyTypeSpecifier(type)));
userData.Add(key);
}
_subscription2 = _plcWrapper.AdsClient.WhenNotificationEx(list, NotificationSettings.ImmediatelyOnChange, userData.ToArray())
.Subscribe(_observerValueNotification);
}
}
I'm using ValueNotification simply because, I'd like to use this pattern also for complex PLC Variables like Structs.
As You can see, in the WhenNotificationEx method I'm using UserData[] to provide some sort of identification of what Variable has changed when handling the change.
My idea was to use Handle property from ValueNotification as an indexer in UserData[] to identify what variable I'm dealing with, but for some reason Handle starts from 2.
My question is, is it expected behaviour, does the Handle value really always start from 2?
I've decided that relying on the Handle being index in the UserData array is quite unpredictable as Handle is being created by the Twincat Ads server.
Solved the issue by creating own extension method to the WhenNotificationEx. Turned out IDisposableHandleBag has exactly what I was looking for, which is SourceResultHandles property, where AnySymbolSpecifier and ResultHandle are both stored!
Here's created extension method
public static Dictionary<string, uint> Handles { get; private set; } = new();
public static IObservable<ValueNotification> WhenNotificationWithHandle(this IAdsConnection connection, IList<AnySymbolSpecifier> symbols, NotificationSettings settings)
{
IAdsConnection connection2 = connection;
IList<AnySymbolSpecifier> symbols2 = symbols;
NotificationSettings settings2 = settings;
if (connection2 == null)
{
throw new ArgumentNullException("connection");
}
if (symbols2 == null)
{
throw new ArgumentNullException("symbols");
}
if (symbols2.Count == 0)
{
throw new ArgumentOutOfRangeException("symbols", "Symbol list is empty!");
}
IDisposableHandleBag<AnySymbolSpecifier> bag = null;
EventLoopScheduler scheduler = new EventLoopScheduler();
IObservable<int> whenSymbolChangeObserver = connection2.WhenSymbolVersionChanges(scheduler);
IDisposable whenSymbolChanges = null;
Action<EventHandler<AdsNotificationExEventArgs>> addHandler = delegate (EventHandler<AdsNotificationExEventArgs> h)
{
connection2.AdsNotificationEx += h;
bag = ((IAdsHandleCacheProvider)connection2).CreateNotificationExHandleBag(symbols2, relaxSubErrors: false, settings2, null);
bag.CreateHandles();
// Collect Handles
Handles.Clear();
foreach (var item in bag.SourceResultHandles)
Handles.Add(item.source.InstancePath, item.result.Handle);
whenSymbolChanges = whenSymbolChangeObserver.Subscribe((Action<int>)delegate
{
bag.CreateHandles();
Handles.Clear();
foreach (var item in bag.SourceResultHandles)
Handles.Add(item.source.InstancePath, item.result.Handle);
}, (Action<Exception>)delegate
{
TcTraceSource traceAds = AdsModule.TraceAds;
DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(101, 1);
defaultInterpolatedStringHandler.AppendLiteral("The AdsServer '");
defaultInterpolatedStringHandler.AppendFormatted(connection2.Address);
defaultInterpolatedStringHandler.AppendLiteral("' doesn't support SymbolVersionChanged Notifications! Handle recreation is not active!");
traceAds.TraceInformation(defaultInterpolatedStringHandler.ToStringAndClear());
});
};
Action<EventHandler<AdsNotificationExEventArgs>> removeHandler = delegate (EventHandler<AdsNotificationExEventArgs> h)
{
if (whenSymbolChanges != null)
{
whenSymbolChanges.Dispose();
}
scheduler.Dispose();
if (bag != null)
{
bag.Dispose();
bag = null;
Handles.Clear();
}
connection2.AdsNotificationEx -= h;
};
return from ev in Observable.FromEventPattern<EventHandler<AdsNotificationExEventArgs>, AdsNotificationExEventArgs>(addHandler, removeHandler)
where bag.Contains(ev.EventArgs.Handle)
select new ValueNotification(ev.EventArgs, ev.EventArgs.Value);
}

How to handle New transaction is not allowed because there are other threads running in the session for multiple calls or to save as list of Entities

Hi I am using Entity Framework Code First, I have a collection of Entities that need to be saved, but I have my EF Repository created as below
public T Create(T item)
{
try
{
if (ufb != null && ufb.CurrentUser != null)
{
SetValue("CreatedByUserId", item, ufb.CurrentUser.Id);
SetValue("UpdatedByUserId", item, ufb.CurrentUser.Id);
}
SetValue("DateCreated", item, DateTime.Now);
SetValue("DateUpdated", item, DateTime.Now);
var newEntry = this.DbSet.Add(item);
this.Context.Database.Log = message => LogHandler.LogInfo(1111, message);
try
{
this.Context.SaveChanges();
}
catch (Exception ex)
{
LogHandler.LogInfo(2501, ex.Message);
}
BuildMetaData(item, true, true);
return newEntry;
}
catch (DbEntityValidationException dbEx)
{
// http://forums.asp.net/t/2014382.aspx?Validation+failed+for+one+or+more+entities+See+EntityValidationErrors+property+for+more+details+
string msg = string.Empty;
foreach (var validationErrors in dbEx.EntityValidationErrors)
{
foreach (var validationError in validationErrors.ValidationErrors)
{
msg += validationError.PropertyName;
msg += "---";
msg += validationError.ErrorMessage;
msg += "||";
}
}
throw new Exception("7777 CREATE EntityValidationErrors: " + msg);
}
}
My calling method is as below:
public List<VehicleInfo> Create(List<VehicleInfo> vehicleInfos, string Entity, int EntityId)
{
bool vehicleExists = false; List<VehicleInfo> newVehicleInfos = null;
if ((vehicleInfos != null) && (vehicleInfos.Count > 0))
{
newVehicleInfos = new List<VehicleInfo>();
foreach (VehicleInfo vehicleInfo in vehicleInfos)
{
vehicleExists = false;
if (vehicleInfo != null)
{
vehicleExists = this.VehicleExists(vehicleInfo.VehicleId, Entity, EntityId);
vehicleInfo.Entity = Entity;
vehicleInfo.EntityId = EntityId;
VehicleInfo v = this.UnitOfWork.VehicleInfoRepository.Create(vehicleInfo);
newVehicleInfos.Add(v);
}
}
}
return newVehicleInfos;
}
Hence when I am calling repositories create method for multiple times, its throwing me the above error, any help or suggestion would be very helpful, please thank you.
void BuildMetaDataNoThread(object item, bool active, bool isNew = false)
{
if (item.GetType() != typeof(JsonObject))
{
var dm = new DataAccessUnitOfWork(Constants.DefaultConnection);
var qtype = item.GetType();
if (qtype.BaseType.BaseType != null)
{
if ((isNew && qtype.BaseType.Name == typeof(ModelBase).Name) | qtype.BaseType.BaseType.Name == typeof(ModelBase).Name)
{
Thread.Sleep(500);
//collect data
var element = (ModelBase)item;
element.BuildMetaData(DataRequestType.CurrentItem);
var records = ModelBase.MetaData;
ModelBase.MetaData = new List<ModelRecord> { };
if (records == null) return;
foreach (ModelRecord r in records)
{
if (r!=null)
{
var jsr = new JavaScriptSerializer();
//object meta = r;
object rdata = r.Data;
var type = rdata.GetType();
var token = type.BaseType.Name;
List<string> include = r.Include;
// Cycle-through clieanup of models to be encoded into Json Data.
// this helper eliminates infinate relations by including records specified
// by a list of strings
if (include.Where(x => x.Contains("CreatedByUser")).Count() == 0)
include.Add("CreatedByUser");
if (include.Where(x => x.Contains("UpdatedByUser")).Count() == 0)
include.Add("UpdatedByUser");
var data = ClassCloner.CollectData(rdata, include);
List<string> tags = ClassCloner.CollectTags(data);
string _tags = "";
tags.ForEach((xtm) =>
{
_tags += xtm + ',';
});
var json = jsr.Serialize(data);
int id = 0;
//get identity
foreach (var prop in type.GetProperties())
{
if (id == 0)
{
foreach (var cp in prop.CustomAttributes)
{
if (cp.AttributeType.Name == "KeyAttribute")
{
var _id = ((Dictionary<string, object>)data)[prop.Name];
id = (int)_id;
break;
}
}
}
else { break; }
}
var query = dm.JsonObjectRepository.GetAll();
var key = "_" + token;
var _data = (Dictionary<string, object>)data;
var ExistingMetaData = (from x in query where x.SourceKey == key && x.SourceId == id select x).FirstOrDefault();
if (ExistingMetaData != null)
{
if (_data.ContainsKey("DateUpdated")) ExistingMetaData.Date = (DateTime)_data["DateUpdated"];
ExistingMetaData.SourceData = data;
ExistingMetaData.Encode();
ExistingMetaData.Active = active;
ExistingMetaData.SearchTags = _tags;
dm.JsonObjectRepository.Update(ExistingMetaData);
}
else
{
var newData = new JsonObject
{
Active = true,
Date = (DateTime)_data["DateUpdated"],
SourceData = data,
SourceId = id,
SourceKey = key,
SearchTags = _tags,
TargetKey = "GlobalSearchMetaData"
};
newData.Encode();
dm.JsonObjectRepository.Create(newData);
}
}
}
}
}
}
}
void BuildMetaData(object dataRecord, bool active, bool isNew)
{
new Thread((item) => { BuildMetaDataNoThread(item, active, isNew); }).Start(dataRecord);
}

Unsupported Media Type 415 Error in Angular 7 & .NET Core API

I'm passing in a selected row to delete in my Angular Application using selection on a material data table. For some reason though, I'm getting a 415 error. Not sure what I'm doing wrong, either on the server or the client side, but I'm not sure even if I'm passing the correct object.
What's the issue here? I'm using Angular 7 for the client and making the API in .NET Core
ActionsController.cs .NET Core
[HttpDelete("deleteRow")]
public Msg DeleteRows(string sessionId, T table, Tag[] rows)
{
try
{
UserState userState = GetUserState(sessionId);
Msg m = CheckTableAccess(sessionId, table, TableAccessLevel.ReadModifyCreateDelete, userState);
if (m.IsNotOk)
return m;
if (table == T.Action)
{
foreach (Tag t in rows)
{
m = CheckUpdatableAction(sessionId, rows[0]);
if (m.IsNotOk)
return m;
}
}
if (table == T.RouteStop)
{
XioTransaction xt = new XioTransaction(userState);
XioWriter xwd = null;
xwd = xt.CreateDeleteWriter(table);
foreach (Tag t in rows)
{
XioTable routeStop = new XioTable(userState, T.RouteStop);
Tag ownerTag = ((DbrRouteStop)routeStop.LoadSingleRow(t, C.RouteStop_RouteTag)).RouteTag;
xwd.DeleteRow(t, ownerTag);
}
xt.WriteAll();
}
else if (table == T.RouteEvent)
{
XioTransaction xt = new XioTransaction(userState);
XioWriter xwd = null;
xwd = xt.CreateDeleteWriter(table);
foreach (Tag t in rows)
{
XioTable routeEvent = new XioTable(userState, T.RouteEvent);
Tag ownerTag = ((DbrRouteEvent)routeEvent.LoadSingleRow(t, C.RouteEvent_RouteTag)).RouteTag;
xwd.DeleteRow(t, ownerTag);
}
xt.WriteAll();
}
else if (table == T.CompanyResource)
{
XioTransaction xt = new XioTransaction(userState);
XioWriter xwd = null;
xwd = xt.CreateDeleteWriter(table);
foreach (Tag t in rows)
{
XioTable cr = new XioTable(userState, T.CompanyResource);
DbrCompanyResource crRec = (DbrCompanyResource)cr.LoadSingleRow(t, C.CompanyResource_CompanyTag, C.CompanyResource_Tab);
XioTable xtr = new XioTable(userState, crRec.Tab);
// the critical where is on divisiontag and all tables that are passed in will have a divion tag
// luckily the code will just look at the field name
xtr.WhereList.Where(C.Driver_DivisionTag, ComparisonOp.EqualTo, crRec.CompanyTag);
xtr.LoadData();
if (xtr.GetAllRows().Length > 0)
return new Msg(M.ResourcesExistAtCompanyLevel);
xwd.DeleteRow(t);
}
xt.WriteAll();
}
else
DbRow.DeleteRecursive(userState, table, rows);
userState.Completed(LogEntryType.DeleteRows, null);
}
catch (MsgException e)
{
return e.Msg;
}
catch (SqlException e)
{
if (e.Number == 547)
{
return new Msg(M.CannotDeleteOwnerRowWithComponent);
}
else
return new Msg(M.UnexpectedViewDeleteError, e.ToString());
}
catch (Exception e)
{
return new Msg(M.UnexpectedViewDeleteError, e.ToString());
}
return Msg.Ok;
}
ViewComponent.ts
export class ViewComponent implements OnInit, OnDestroy {
// User Fields
currentUser: User;
users: User[] = [];
currentUserSubscription: Subscription;
loading : boolean;
// Action Fields
viewData: any;
viewName: string;
refNumber: number;
currentActionSubscription: Subscription;
displayedColumns: string[] = [];
dataSource: any = new MatTableDataSource([]);
pageSizeOptions: number[] = [10, 20, 50];
#ViewChild(MatSort) sort: MatSort;
#ViewChild(MatPaginator) paginator: MatPaginator;
selection = new SelectionModel<TableRow>(true, []);
defaultSort: MatSortable = {
id: 'defColumnName',
start: 'asc',
disableClear: true
};
defaultPaginator: MatPaginator;
constructor(
private iconRegistry: MatIconRegistry,
private sanitizer: DomSanitizer,
private actionService: ActionService
) {
this.loading = false;
this.iconRegistry.addSvgIcon(
'thumbs-up',
this.sanitizer.bypassSecurityTrustResourceUrl(
'assets/img/examples/thumbup-icon.svg'
)
);
}
loadAction(action: any) {
this.loading = true;
// If there is already data loaded into the View, cache it in the service.
if (this.viewData) {
this.cacheAction();
}
if (this.sort) {
// If there is sorting cached, load it into the View.
if (action.sortable) {
// If the action was cached, we should hit this block.
this.sort.sort(action.sortable);
} else {
// Else apply the defaultSort.
this.sort.sort(this.defaultSort);
}
}
if (this.paginator) {
// If we've stored a pageIndex and/or pageSize, retrieve accordingly.
if (action.pageIndex) {
this.paginator.pageIndex = action.pageIndex;
} else { // Apply default pageIndex.
this.paginator.pageIndex = 0;
}
if (action.pageSize) {
this.paginator.pageSize = action.pageSize;
} else { // Apply default pageSize.
this.paginator.pageSize = 10;
}
}
// Apply the sort & paginator to the View data.
setTimeout(() => this.dataSource.sort = this.sort, 4000);
setTimeout(() => this.dataSource.paginator = this.paginator, 4000);
// Load the new action's data into the View:
this.viewData = action.action;
this.viewName = action.action.ActionName;
this.refNumber = action.refNumber;
// TODO: add uniquifiers/ids and use these as the sort for table
const displayedColumns = this.viewData.Columns.map((c: { Name: any; }) => c.Name);
displayedColumns[2] = 'Folder1';
this.displayedColumns = ['select'].concat(displayedColumns);
// tslint:disable-next-line: max-line-length
const fetchedData = this.viewData.DataRows.map((r: { slice: (arg0: number, arg1: number) => { forEach: (arg0: (d: any, i: string | number) => any) => void; }; }) => {
const row = {};
r.slice(0, 9).forEach((d: any, i: string | number) => (row[this.displayedColumns[i]] = d));
return row;
});
this.dataSource = new MatTableDataSource(fetchedData);
this.loading = false;
}
// Stores the current Action, sort, and paginator in an ActionState object to be held in the action service's stateMap.
cacheAction() {
let actionState = new ActionState(this.viewData);
// Determine the sort direction to store.
let cachedStart: SortDirection;
if (this.sort.direction == "desc") {
cachedStart = 'desc';
} else {
cachedStart = 'asc';
}
// Create a Sortable so that we can re-apply this sort.
actionState.sortable = {
id: this.sort.active,
start: cachedStart,
disableClear: this.sort.disableClear
};
// Store the current pageIndex and pageSize.
actionState.pageIndex = this.paginator.pageIndex;
actionState.pageSize = this.paginator.pageSize;
// Store the refNumber in the actionState for later retrieval.
actionState.refNumber = this.refNumber;
this.actionService.cacheAction(actionState);
}
ngOnInit() {
// Subscribes to the action service's currentAction, populating this component with View data.
this.actionService.currentAction.subscribe(action => this.loadAction(action));
}
/** Whether the number of selected elements matches the total number of rows. */
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
/** Selects all rows if they are not all selected; otherwise clear selection. */
masterToggle() {
this.isAllSelected()
? this.selection.clear()
: this.dataSource.data.forEach((row: TableRow) => this.selection.select(row));
}
// Delete row functionality
deleteRow() {
console.log(this.selection);
this.selection.selected.forEach(item => {
const index: number = this.dataSource.data.findIndex((d: TableRow) => d === item);
console.log(this.dataSource.data.findIndex((d: TableRow) => d === item));
this.dataSource.data.splice(index, 1);
this.dataSource = new MatTableDataSource<Element>(this.dataSource.data);
});
this.selection = new SelectionModel<TableRow>(true, []);
this.actionService.deleteRow(this.selection).subscribe((response) => {
console.log('Success!');
});
}
ActionsService.ts
deleteRow(selection: any): Observable<{}> {
console.log('testing service');
// create an array of query params using the property that you use to identify a table row
const queryParams = [selection._selection].map((row: { value: any; }) => `id=${row.value}`);
// add the query params to the url
const url = `http://localhost:15217/actions/deleteRow`;
return this.http.delete<any>(url);
}
You need to add an http header to specify the content type for your http request body. If you are sending a json body, the header is content-type: application/json
You can update actionService.ts
deleteRow(selection: any): Observable<{}> {
let headers = new Headers();
headers.append('Content-Type', 'application/json');
const queryParams = [selection._selection].map((row: { value: any; }) => `id=${row.value}`);
const url = `http://localhost:15217/actions/deleteRow`;
const options = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
}),
body: {}
}
return this.http.delete<any>(url, options);
}

Creating tree view dynamically according to json text in Winforms

I am building an application that gets in run-time JSON message from external source.
I don't know anything about the structure of the message text.
I want to take this JSON text, render it to a tree view (or something equivalent, UI regarding),
edit this JSON in that tree view that I just dynamically created, and send the text back to the source.
I really don't know where to start..Any suggestions?
private void btn_Convert_MouseClick(object sender, MouseEventArgs e)
{
try
{
string json = rbt_display.Text;
JObject obj = JObject.Parse(json);
tvw_display.Nodes.Clear();
TreeNode parent = Json2Tree(obj);
parent.Text = "Root Object";
tvw_display.Nodes.Add(parent);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "ERROR");
}
}
private TreeNode Json2Tree(JObject obj)
{
//create the parent node
TreeNode parent = new TreeNode();
//loop through the obj. all token should be pair<key, value>
foreach (var token in obj)
{
//change the display Content of the parent
parent.Text = token.Key.ToString();
//create the child node
TreeNode child = new TreeNode();
child.Text = token.Key.ToString();
//check if the value is of type obj recall the method
if (token.Value.Type.ToString() == "Object")
{
// child.Text = token.Key.ToString();
//create a new JObject using the the Token.value
JObject o = (JObject)token.Value;
//recall the method
child = Json2Tree(o);
//add the child to the parentNode
parent.Nodes.Add(child);
}
//if type is of array
else if (token.Value.Type.ToString() == "Array")
{
int ix = -1;
// child.Text = token.Key.ToString();
//loop though the array
foreach (var itm in token.Value)
{
//check if value is an Array of objects
if (itm.Type.ToString() == "Object")
{
TreeNode objTN = new TreeNode();
//child.Text = token.Key.ToString();
//call back the method
ix++;
JObject o = (JObject)itm;
objTN = Json2Tree(o);
objTN.Text = token.Key.ToString() + "[" + ix + "]";
child.Nodes.Add(objTN);
//parent.Nodes.Add(child);
}
//regular array string, int, etc
else if(itm.Type.ToString() == "Array")
{
ix++;
TreeNode dataArray = new TreeNode();
foreach (var data in itm)
{
dataArray.Text = token.Key.ToString() + "[" + ix + "]";
dataArray.Nodes.Add(data.ToString());
}
child.Nodes.Add(dataArray);
}
else
{
child.Nodes.Add(itm.ToString());
}
}
parent.Nodes.Add(child);
}
else
{
//if token.Value is not nested
// child.Text = token.Key.ToString();
//change the value into N/A if value == null or an empty string
if (token.Value.ToString() == "")
child.Nodes.Add("N/A");
else
child.Nodes.Add(token.Value.ToString());
parent.Nodes.Add(child);
}
}
return parent;
}
sample json
{
"firstName": "John",
"lastName": "Smith",
"isAlive": true,
"age": 25,
"height_cm": 167.6,
"address": {
"streetAddress": "21 2nd Street",
"city": "New York",
"state": "NY",
"postalCode": "10021-3100"
},
"phoneNumbers": [
{
"type": "home",
"number": "212 555-1234"
},
{
"type": "office",
"number": "646 555-4567"
}
],
"children": [],
"spouse": null
}
Note: This example uses NewtonSoft Json. Right-click solution and click manage NuGet packages to install the reference.
This code will handle both JArray or JObject as an input:
string jsonString = "your json string here";
string rootName = "root", nodeName = "node";
JContainer json;
try {
if (jsonString.StartsWith("["))
{
json = JArray.Parse(jsonString);
treeView1.Nodes.Add(Utilities.Json2Tree((JArray)json, rootName, nodeName));
}
else
{
json = JObject.Parse(jsonString);
treeView1.Nodes.Add(Utilities.Json2Tree((JObject)json, text));
}
}
catch(JsonReaderException jre)
{
MessageBox.Show("Invalid Json.");
}
public class Utilities
{
public static TreeNode Json2Tree(JArray root, string rootName = "", string nodeName="")
{
TreeNode parent = new TreeNode(rootName);
int index = 0;
foreach(JToken obj in root)
{
TreeNode child = new TreeNode(string.Format("{0}[{1}]", nodeName, index++));
foreach (KeyValuePair<string, JToken> token in (JObject)obj)
{
switch (token.Value.Type)
{
case JTokenType.Array:
case JTokenType.Object:
child.Nodes.Add(Json2Tree((JObject)token.Value, token.Key));
break;
default:
child.Nodes.Add(GetChild(token));
break;
}
}
parent.Nodes.Add(child);
}
return parent;
}
public static TreeNode Json2Tree(JObject root, string text = "")
{
TreeNode parent = new TreeNode(text);
foreach (KeyValuePair<string, JToken> token in root)
{
switch (token.Value.Type)
{
case JTokenType.Object:
parent.Nodes.Add(Json2Tree((JObject)token.Value, token.Key));
break;
case JTokenType.Array:
int index = 0;
foreach(JToken element in (JArray)token.Value)
{
parent.Nodes.Add(Json2Tree((JObject)element, string.Format("{0}[{1}]", token.Key, index++)));
}
if (index == 0) parent.Nodes.Add(string.Format("{0}[ ]", token.Key)); //to handle empty arrays
break;
default:
parent.Nodes.Add(GetChild(token));
break;
}
}
return parent;
}
private static TreeNode GetChild(KeyValuePair<string, JToken> token)
{
TreeNode child = new TreeNode(token.Key);
child.Nodes.Add(string.IsNullOrEmpty(token.Value.ToString()) ? "n/a" : token.Value.ToString());
return child;
}
}
You can try this code :
public class JsonTag
{
public JsonTag(JsonReader reader)
{
TokenType = reader.TokenType;
Value = reader.Value;
ValueType = reader.ValueType;
}
public JsonToken TokenType { get; set; }
public object Value { get; set; }
public Type ValueType { get; set; }
}
private void JsonToTreeview(string json)
{
tvwValue.BeginUpdate();
var parentText = string.Empty;
TreeNodeCollection parentNodes = tvwValue.Nodes;
TreeNode current = null;
tvwValue.Nodes.Clear();
var reader = new JsonTextReader(new StringReader(json));
while (reader.Read())
{
switch (reader.TokenType)
{
case JsonToken.None:
break;
case JsonToken.StartObject:
current = new TreeNode("{}") { Tag = new JsonTag(reader) };
parentNodes.Add(current);
parentNodes = current.Nodes;
break;
case JsonToken.StartArray:
current = new TreeNode("[]") { Tag = new JsonTag(reader) };
parentNodes.Add(current);
if (current.PrevNode != null)
{
if (((JsonTag)current.PrevNode.Tag).TokenType == JsonToken.PropertyName)
current.Parent.Text += "[]";
parentText = current.Parent.Text;
if (current.Parent.Parent.Text.Length > 2)
parentText = ", " + parentText;
current.Parent.Parent.Text = current.Parent.Parent.Text.Insert(current.Parent.Parent.Text.Length - 1, parentText);
}
parentNodes = current.Nodes;
break;
case JsonToken.StartConstructor:
break;
case JsonToken.PropertyName:
current = new TreeNode("\"" + reader.Value + "\" : ");
parentNodes.Add(current);
if (current.PrevNode != null)
current.PrevNode.Text += ",";
parentNodes = current.Nodes;
current = new TreeNode(reader.Value.ToString()) { Tag = new JsonTag(reader) };
parentNodes.Add(current);
break;
case JsonToken.Comment:
break;
case JsonToken.Raw:
break;
case JsonToken.Date:
case JsonToken.Integer:
case JsonToken.Float:
case JsonToken.Boolean:
case JsonToken.String:
var readerValue = "";
if (reader.TokenType == JsonToken.String)
readerValue = "\"" + reader.Value + "\"";
else
readerValue = reader.Value.ToString();
current = new TreeNode(readerValue) { Tag = new JsonTag(reader) };
parentNodes.Add(current);
current.Parent.Text += readerValue;
parentText = current.Parent.Text;
if (current.Parent.Parent.Text.Length > 2)
parentText = ", " + parentText;
current.Parent.Parent.Text = current.Parent.Parent.Text.Insert(current.Parent.Parent.Text.Length - 1, parentText);
if (((JsonTag)current.PrevNode.Tag).TokenType == JsonToken.PropertyName)
current = current.Parent;
current = current.Parent;
parentNodes = current.Nodes;
break;
case JsonToken.Bytes:
break;
case JsonToken.Null:
break;
case JsonToken.Undefined:
break;
case JsonToken.EndObject:
if (current.FirstNode.Tag != null &&
((JsonTag)current.FirstNode.Tag).TokenType == JsonToken.PropertyName)
current = current.Parent;
current = current.Parent;
if (current == null)
parentNodes = tvwValue.Nodes;
else
parentNodes = current.Nodes;
break;
case JsonToken.EndArray:
if (((JsonTag)current.PrevNode.Tag).TokenType == JsonToken.PropertyName)
current = current.Parent;
current = current.Parent;
if (current == null)
parentNodes = tvwValue.Nodes;
else
parentNodes = current.Nodes;
break;
case JsonToken.EndConstructor:
break;
default:
throw new ArgumentOutOfRangeException();
}
}
tvwValue.EndUpdate();
}
Lots of questions there, really. If you really need guidance on every part of that then it's a lot to try and answer here.
There are classes for reading JSON structures, readily available. As Yosi indirectly linked, there's JSON.net
Once you can read the JSON, you can use it to construct the TreeView
Editing is simple enough, as the TreeView has a property for LabelEdit that supports editing in-place. From there, it's just a matter of reacting to that and keeping track of the changes. Or perhaps reading it all back out in one fell swoop at the end, your choice. Either way, the TreeView has events such as BeforeLabelEdit, AfterLabelEdit, etc., all of which can be found on the TreeView link above.
From the package manager console:
PM> Install-Package Newtonsoft.Json
Then cleaning up #vinceg 's answer, I rolled a static class:
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.Windows.Forms;
public static class clsJSON2treeview
{
/// <summary>Parse JSON string, individual tokens become TreeView Nodes ~mwr</summary>
/// <param name="oTV">TreeView control to display parsed JSON</param>
/// <param name="sJSON">Incoming JSON string</param>
/// <param name="rootName">Title of top node in TreeView wrapping all JSON</param>
public static void JsonToTreeview(TreeView oTV, string sJSON, string rootName)
{
JContainer json = sJSON.StartsWith("[")
? (JContainer)JArray.Parse(sJSON)
: (JContainer)JObject.Parse(sJSON);
oTV.Nodes.Add(Ele2Node(json, rootName));
}
private static TreeNode Ele2Node(object oJthingy, string text = "")
{
TreeNode oThisNode = new TreeNode(text);
switch (oJthingy.GetType().Name) //~mwr could not find parent object for all three JObject, JArray, JValue
{
case "JObject":
foreach (KeyValuePair<string, JToken> oJtok in (JObject)oJthingy)
oThisNode.Nodes.Add(Ele2Node(oJtok.Value, oJtok.Key));
break;
case "JArray":
int i = 0;
foreach (JToken oJtok in (JArray)oJthingy)
oThisNode.Nodes.Add(Ele2Node(oJtok, string.Format("[{0}]", i++)));
if (i == 0) oThisNode.Nodes.Add("[]"); //to handle empty arrays
break;
case "JValue":
oThisNode.Nodes.Add(new TreeNode(oJthingy.ToString()));
break;
default:
throw new System.Exception("clsJSON2Treeview can't interpret object:" + oJthingy.GetType().Name);
}
return oThisNode;
}
}

Looping trought a list of objects in C# in a better way?

I have a foreach loop that loops trought a list of objects. The meaning of it is to set a NavigateUrl to a Hyperlink. My code looks like this:
foreach (var con in contacts)
{
if (con.ContactTypeID == 1)
{
FacebookIcon.NavigateUrl = "http://facebook.com/" + con.ContactURL;
}
}
I wonder if their is some better way to do it. I will have about 10 other ContactTypeID and I rather don't write nine more if else..
You could use LINQ:
var facebookURL = contacts.Where(c => c.ContactTypeID == 1)
.Select(c => c.url)
.FirstOrDefault();
if(facebookURL != null)
FacebookIcon.NavigateUrl = "http://facebook.com/" + facebookURL;
Edit: Actually you could benefit of LINQ's deferred execution to reuse the same for every type of contact-type:
var contactType = 1; // facebook
var url = contacts.Where(c => c.ContactTypeID == contactType)
.Select(c => c.url);
if (url.Any())
FacebookIcon.NavigateUrl = "http://facebook.com/" + url.First();
contactType = 2; // google
if (url.Any())
GoogleIcon.NavigateUrl = "http://Google.com/" + url.First();
Edit 2: Here's another approach using a Dictionary mapping all types with their URLs which should be more efficient in case you have millions of types ;-) (#MAfifi):
var urlTypeMapping = contacts.GroupBy(c => c.ContactTypeID)
.ToDictionary(grp => grp.Key, grp => grp.Select(c => c.url));
foreach (var type in urlTypeMapping)
{
var typeUrl = type.Value.FirstOrDefault();
if (typeUrl != null)
{
switch (type.Key)
{
case 1:
FacebookIcon.NavigateUrl = "http://facebook.com/" + typeUrl;
break;
case 2:
GoogleIcon.NavigateUrl = "http://Google.com/" + typeUrl;
break;
default:
break; //or throw new Exception("Invalid type!");
}
}
}
You could use LINQ in order to do what you want.
var x = contacts.FirstOrDefault (c => c.ContactTypeID == 1);
if( x != null )
{
FacebookIcon.NavigateUrl = String.Format ("http://facebook.com/{0}", x.ContactURL);
}
You can use switch
switch (caseSwitch)
{
case 1:
FacebookIcon.NavigateUrl = "http://facebook.com/" + con.ContactURL;
break;
case 2:
//
break;
default:
//
break;
}
you can use linq.
var con = contacts.FirsOrDefault(c => c.ContactTypeID.Equals(1));
if (con == null)
{
return;
}
con.NavigateUrl = "http://facebook.com/" + con.ContactURL;
Or if you have more id's
List<int> ids = new List<int> {1,2,5,7};
contacts.Where(c => ids.Containt(c.ContactTypeID)).ToList().ForEach(item => item.NavigateUrl = "http://facebook.com/" + item.ContactURL);
If you use Linq You should use First or FirstOrDefault
var url = contacts.FirstOrDefault(c => c.ContactTypeID == 1).NavigateUrl;
Use a switch:
foreach (var con in contacts)
{
switch (con.ContactTypeID)
{
case 1:
FacebookIcon.NavigateUrl = "http://facebook.com/" + con.ContactURL;
break;
case 2:
. . .
break;
. . .
}
}
Perhapse a condensed LINQ:
contacts.ForEach(c => { if (c.ContactTypeID == 1) FacebookIcon.NavigateUrl = "http://facebook.com/" + con.ContactURL; });
If you want to do "it" for each ContactTypeID == 1.
I assume that each contact is not necessarily Facebook and you need to dynamically set different attributes based on what the contact is?
Your best bet is a Dictionary<int, Action> or similar, where you just do something like,
var setCorrectUrl = new Dictionary<int, Action<Contact>>
{
// Appropriate entries in here, e.g. (syntax not quite right)
{
1,
(contact) => FacebookIcon.NavigateUrl = contact.ContactURL;
}
}
foreach (var con in contacts)
{
setCorrectUrl[con.ContactTypeID](con);
}
Well, I would do some refactoring of the code.
Imagine he would have 10 other types to implement :)
The solutions provided above are workable but not very elegant in terms of extensibility.
So, here is my solution:
1) Implement a base class with the common contact properties
public abstract class BaseContact
{
public string Name { get; set; }
public abstract string Url { get; set; }
}
2) Implement the concrete types
public class FbContact : BaseContact
{
private string _baseUrl = "http://facebook.com/{0}";
private string _url = string.Empty;
public override string Url
{
get { return _url; }
set { _url = string.Format(_baseUrl, value); }
}
}
public class LinkedInContact : BaseContact
{
private string _baseUrl = "http://linkedin.com/{0}";
private string _url = string.Empty;
public override string Url
{
get { return _url; }
set { _url = string.Format(_baseUrl, value); }
}
}
3) This is just a helper class for setting the navigation url
public static class NavigationCreator
{
public static void SetUrl(BaseContact contact, HyperLink link)
{
link.NavigateUrl = contact.Url;
}
}
4) Some test code to visualize the result
List<BaseContact> items = new List<BaseContact>();
for (int i = 0; i < 5; i++)
{
BaseContact item;
if (i % 2 == 0) item = new FbContact(); else item = new LinkedInContact();
item.Url = "My name " + i;
items.Add(item);
}
foreach (var contact in items)
{
HyperLink link = new HyperLink();
NavigationCreator.SetUrl(contact, link);
Console.WriteLine(link.NavigateUrl);
}
Console.Read();

Categories

Resources