Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
EssOEss
Oct 23, 2006
128-bit approved
Put the code for the logic you want to implement (if param, set header) in the controller action you want to implement it in (or intercept it in a middleware/filter component or whatnot). I assume there is some obstacle in this for you in your scenario - where exactly?

Adbot
ADBOT LOVES YOU

LOOK I AM A TURTLE
May 22, 2003

"I'm actually a tortoise."
Grimey Drawer

EssOEss posted:

Put the code for the logic you want to implement (if param, set header) in the controller action you want to implement it in (or intercept it in a middleware/filter component or whatnot). I assume there is some obstacle in this for you in your scenario - where exactly?

Sorry, I should've mentioned this approach in my question. There's no particular obstacle, but this feels like a scenario that might have been supported using the built-in API, and that I just wasn't seeing it. I try to avoid dealing with headers manually whenever possible, but it looks like I'll have to make an exception here.

LOOK I AM A TURTLE fucked around with this message at 16:25 on Jan 2, 2018

epswing
Nov 4, 2003

Soiled Meat

LongSack posted:

Yeah, but you have to compose the document with controls like grids, borders, list boxes/views, textblocks, etc., and if you want good looking output you have to do things like keep track of how high each line is so you know where to put the next one. It is drearily time consuming, thus my quest for something that would make it easier.

Do they need to look "nice", and include things like auto-width and text wrapping? Or can your reports use monospace font, and fixed width columns?

Xik
Mar 10, 2011

Dinosaur Gum

EssOEss posted:

I would just fake it with a worker thread - your main code thinks it is some async operation going on but really, all that happens is that a new thread is started and the work done on that thread in parallel.

This would be Task.Run(() => SyncOperation(), TaskOptions.LongRunning), which returns a Task you can await. You want to specify LongRunning (which starts a new thread every time) so you do not eat up thread pool threads with long blocking synchronous network operations (my rule of thumb: 10+ milliseconds per call is long running).

Depending on your performance needs, the model might be futher optimized but the above should be a good starting point.

I'm not sure why I couldn't get this to work before I went on the xmas break, but I'm back at work this morning and got it working in no time, so thank you. Here's a simplified example what I did:

code:
var properAsyncTask = ProperAsyncFunction(foo);
var anotherProperAsyncTask = AnotherProperAsyncFunction(bar);
var ghettoTask = Task.Factory.StartNew(() => NetworkCallThroughNotAsyncLibrary(foobar), TaskCreationOptions.LongRunning);
// They are all off doing their thing....

// Then when I need them...
var properAsyncResult = await properAsyncTask;
var anotherProperAsyncResult = await anotherProperAsyncTask;
var ghettoResult = await ghettoTask;
I also read about potential performance pitfalls of ~real~ threads, but the ghettoTask is so long it's definitely worth it in this particular case. I'd still be interested if anyone has any input or warnings about doing it this way.

LongSack
Jan 17, 2003

epalm posted:

Do they need to look "nice", and include things like auto-width and text wrapping? Or can your reports use monospace font, and fixed width columns?

A monospace font would solve a number of problems, but I must confess that it would irritate me looking at pages of Courier New and I'd end up rewriting it anyway. :shrug:

Nth Doctor
Sep 7, 2010

Darkrai used Dream Eater!
It's super effective!


LongSack posted:

A monospace font would solve a number of problems, but I must confess that it would irritate me looking at pages of Courier New and I'd end up rewriting it anyway. :shrug:

I suggest using Envy Code R then. Font is gorgeous.

Dietrich
Sep 11, 2001

Nth Doctor posted:

I suggest using Envy Code R then. Font is gorgeous.

Fira Code with Ligatures support. :colbert:

Mr Shiny Pants
Nov 12, 2012
So I've been treating F# DUs as Enums because it is very handy.

An example:
Let's say I have a type of Foo and it has a list of capabilities. I just fill the list with DUs like [canBar;canFoo] and just check if the list contains the DU. This works because of F#s structural comparison, I mean the DUs are different instances right?
Is this dumb?

NihilCredo
Jun 6, 2011

iram omni possibili modo preme:
plus una illa te diffamabit, quam multæ virtutes commendabunt

Mr Shiny Pants posted:

So I've been treating F# DUs as Enums because it is very handy.

An example:
Let's say I have a type of Foo and it has a list of capabilities. I just fill the list with DUs like [canBar;canFoo] and just check if the list contains the DU. This works because of F#s structural comparison, I mean the DUs are different instances right?

Yes and no. Empty DU tags (with no objects attached, e.g. the A in type T = A | B of int) get compiled to singletons as an optimization, so in fact they are the same instance, you can check it by explicily calling System.Object.ReferenceEquals(x, y). So technically, A = A would work even if the discriminated unions did not get structural comparison; in the example above, the structural .Equals() implementations are what allows B 0 = B 0 to work.

quote:

Is this dumb?

No, it's totally fine. Argu works like that, for example.

Mr Shiny Pants
Nov 12, 2012

NihilCredo posted:

Yes and no. Empty DU tags (with no objects attached, e.g. the A in type T = A | B of int) get compiled to singletons as an optimization, so in fact they are the same instance, you can check it by explicily calling System.Object.ReferenceEquals(x, y). So technically, A = A would work even if the discriminated unions did not get structural comparison; in the example above, the structural .Equals() implementations are what allows B 0 = B 0 to work.


No, it's totally fine. Argu works like that, for example.

Thanks man, that is good to know. They work wonderfully for describing stuff.

I had an epiphany today, I finally figured out partial application and "Functions as values". I knew about them before, but never had a use for them and the concept never "clicked". Now I needed something and wrote it using functions that are partially applied and sent to a Mailboxprocessor for further execution. I wrote a DU in the style of

code:
type message =
    | Filter of filter ('a list -> 'a list) * AsyncReplyChannel<Message>
So I send in a filter function which takes a filter property and a list of mailboxprocessors, I fill the first parameter ( the filter property ), send it over and run the function in the receiving processor with a list of other processors.
That was a bit of an eureka moment. :)
BTW you were right about the interfaces, I got it to work but it was too much of a hassle, now I just decorate my MailboxProcessors with lists of DUs to describe their capabilities.

Mr Shiny Pants
Nov 12, 2012
Am I correct in assuming that do! reuses the current thread and starts an Async Workflow, while Async.Start fires off a new thread on the threadpool?

Ciaphas
Nov 20, 2005

> BEWARE, COWARD :ovr:


I'm doing a thing in C# where I want to have a user submit a c# code fragment that I insert into an otherwise mostly blank function, compile, and evaluate. So a user might put in something like int x = 2 + 2; Logger.Write("2 + 2 = {x}"); and I'd want to inject that into the below function

C# code:
public Record ProcessUserScript(Record input)
{
   Record output = new Record(input);

   // -- USER SCRIPT GOES HERE --

   return output;
}
I've learned, and have code for, the basic idea of taking a fully valid .cs program (usings, namespace and all) and compiling, assembling, and executing it. I have a vague understanding that what I want to do is take the two SyntaxTrees, one for the function body and one for the code fragment, and insert the latter into the former at the proper place by walking the former SyntaxTree and replacing or inserting when I hit that comment.

Firstly, do I have the right idea? Second, does anyone know of any tutorializing that might help? I flailed around a bit last week and while I got both syntax trees, my attempts to combine them kept failing.

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

Ciaphas posted:

I'm doing a thing in C# where I want to have a user submit a c# code fragment that I insert into an otherwise mostly blank function, compile, and evaluate. So a user might put in something like int x = 2 + 2; Logger.Write("2 + 2 = {x}"); and I'd want to inject that into the below function

C# code:
public Record ProcessUserScript(Record input)
{
   Record output = new Record(input);

   // -- USER SCRIPT GOES HERE --

   return output;
}
I've learned, and have code for, the basic idea of taking a fully valid .cs program (usings, namespace and all) and compiling, assembling, and executing it. I have a vague understanding that what I want to do is take the two SyntaxTrees, one for the function body and one for the code fragment, and insert the latter into the former at the proper place by walking the former SyntaxTree and replacing or inserting when I hit that comment.

Firstly, do I have the right idea? Second, does anyone know of any tutorializing that might help? I flailed around a bit last week and while I got both syntax trees, my attempts to combine them kept failing.

Can we take a step back? What is the requirement that has resulted in the conclusion that this is the best solution?

ljw1004
Jan 18, 2005

rum

Ciaphas posted:

I'm doing a thing in C# where I want to have a user submit a c# code fragment that I insert into an otherwise mostly blank function, compile, and evaluate.

The right way to do this would be to use the Roslyn API. Start with the user's code as a string. Ask Roslyn to parse+compile it. Provide a compilation context in which you provide bindings for "input" and "output" and "Logger".

code:
// create a project with the C# code fragment
var workspace = new Microsoft.DotNet.ProjectModel.Workspaces.ProjectJsonWorkspace(SampleProjectsDirectory + "/ConsoleApp1");
var solution = workspace.CurrentSolution;
var project = solution.Projects.Single();
var txt = "int x = 15;\r\nint y = x+2;\r\nSystem.Console.WriteLine(y);\r\n";
project = project.AddDocument("a.csx", txt, null, "c:\\a.csx").WithSourceCodeKind(SourceCodeKind.Script).Project;

// compile it
var comp = await project.GetCompilationAsync(cancel).ConfigureAwait(false);
var result = await Task.Run(() => comp.Emit(fn, cancellationToken: cancel)).ConfigureAwait(false);

// run it!
I'm really just pasting these code fragments to give you a hint about what kind of APIs you should be looking at. I wrote this about two years ago and haven't visited it since, so the APIs might have changed. I was emitting a .exe into a file that I could invoke -- maybe there are ways to emit it to memory and execute it all in-memory. Also, I write this via a "ProjectJsonWorkspace", and given the pace of change in C# project types, I'm sure there are new different project types at the moment.

Ciaphas
Nov 20, 2005

> BEWARE, COWARD :ovr:


New Yorp New Yorp posted:

Can we take a step back? What is the requirement that has resulted in the conclusion that this is the best solution?

Legacy, mostly, along with what I like to term "math poo poo I don't understand at all". Some 30 years ago the more advanced stat analyst customers at the end wanted to be able to write short scripts to make a final modification of their data before it was saved and sent along to the next user/process. It was given to them in PL1 syntax. Some 10 years ago it was updated to use VB through some insane COM shenanigans I don't have any hope of understanding. Flash forward to today, where they're rewriting the entire software package, and one of the requirements is to allow any .NET language script to be used to modify records at various points in processing.


ljw1004 posted:

The right way to do this would be to use the Roslyn API. Start with the user's code as a string. Ask Roslyn to parse+compile it. Provide a compilation context in which you provide bindings for "input" and "output" and "Logger".

[code elided]

I'm really just pasting these code fragments to give you a hint about what kind of APIs you should be looking at. I wrote this about two years ago and haven't visited it since, so the APIs might have changed. I was emitting a .exe into a file that I could invoke -- maybe there are ways to emit it to memory and execute it all in-memory. Also, I write this via a "ProjectJsonWorkspace", and given the pace of change in C# project types, I'm sure there are new different project types at the moment.

Yeah Roslyn stuff is what I was looking at but my examples and code (from memory) look very very different from this. I learned to take a full code string and call CSharpSyntaxTree.ParseText(string) to get a SyntaxTree, then emit that into an Assembly in memory using a MemoryStream, then use reflection to invoke the method in that assembly.

This was the first example I followed: Compiling C# Code into Memory and Executing It with Roslyn. The rabbit hole I've been trying to address, after getting that to work, is modifying that syntax tree before compilation, by adding in the user's code.

(I realize I could simply modify the code strings and put the user's code in there that way but this seemed the more "correct" way to proceed. Happy to be told I'm wrong, though.)

Ciaphas fucked around with this message at 21:18 on Jan 8, 2018

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

Ciaphas posted:

Legacy, mostly, along with what I like to term "math poo poo I don't understand at all". Some 30 years ago the more advanced stat analyst customers at the end wanted to be able to write short scripts to make a final modification of their data before it was saved and sent along to the next user/process. It was given to them in PL1 syntax. Some 10 years ago it was updated to use VB through some insane COM shenanigans I don't have any hope of understanding. Flash forward to today, where they're rewriting the entire software package, and one of the requirements is to allow any .NET language script to be used to modify records at various points in processing.

Sounds like you want plugins. Have you looked at MEF?

Ciaphas
Nov 20, 2005

> BEWARE, COWARD :ovr:


Never heard of it, and I'll look into it, but doesn't writing a plugin imply the author having a VS development environment? Because that's never going to happen in this case.

(Also another requirement I just remembered is that the software be able to read, automatically update/translate, and run VB scripts written for the old software. Yeah :cry:)

Ciaphas
Nov 20, 2005

> BEWARE, COWARD :ovr:


Actually now I notice there's a Microsoft.CodeAnalysis.CSharp.Scripting nuget package that appears at first blush (no devenv on this machine) to do exactly what I want so lol at me for overthinking things as usual :doh:

Ciaphas
Nov 20, 2005

> BEWARE, COWARD :ovr:


update: i was right and now I'm annoyed at myself for trying to reinvent the wheel

Ciaphas posted:

it's equal parts amusing and frustrating that i've been working on learning roslyn stuff so i could do a C# REPL for like a week--messing with syntax trees, emitting assemblies, reflecting on them, walking aforesaid trees, etc--when i realized yesterday there's a NuGet package that does it in like a dozen lines

C# code:
using Microsoft.CodeAnalysis.CSharp.Scripting;

                ScriptState state = CSharpScript.RunAsync(txtCode.Text, ScriptOptions.Default
                    .WithReferences(new MetadataReference[] {
                        MetadataReference.CreateFromFile (typeof(Logger).Assembly.Location)
                    })
                    .WithImports(
                        "ScriptingWindow"
                    )
                ).Result;

                Logger.Write("end of run");

                foreach (var x in state.Variables)
                {
                    Logger.Write($"Variable: {x.Type} {x.Name} = {x.Value}");
                }


:doh:

roflsaurus
Jun 5, 2004

GAOooooooh!! RAOR!!!
So I've been stuck maintaining ASP.Net Webforms for 8 years (I know......). Finally got a chance to work with this new hot Core 2.0 MVC and just wanting to make sure I'm not doing anything too crazy/stupid. Does the below sound correct or am I on the wrong track?

This is for a login system, and I want to extend the default template for account management. I'm hoping to stick with the boilerplate Identity classes and not roll my own Cookie Authentication.

Most of this will be done by UserClaimsPrincipalFactory.GenerateClaimsAsync, and adding additional claims when the user logs in. But in the CookieAuthenticationEvents.OnSigningIn I can write additional properties to CookieSigningInContext.Properties.Items collection - I was going to add items here as well. Edit - might not need it to implement the Force relogin feature,

Password expiry/force change after X days
I was going to store a Password_Expiry_UTC as a claim via UserClaimsPrincipalFactory. Then, setup a Authorization Policy or ActionFilterAttribute to check the value of this claim, and if it's expired, redirect the user to the Change Password page. This would enforce password changing across all actions. (rather than just doing after the Login action).

The reason I'm thinking filter instead of policy is a filter will let me redirect the user to a change password page, rather than just a generic "Unauthorised" page.

IP address whitelisting
I was going to implement IP Address whitelisting at a user level (feature requirement). To do this, I was going to store the IP as a claim when they login, and then detect if this every changes in the CookieAuthenticationEvents.OnValidatePrincipal event. If it changes and is still valid, I will re-check against the list of IPs. If it's valid, then call context.ReplacePrincipal. This will update the claim IP and won't log the user out.

But if it's invalid, it'll destroy the session and user won't be able to login again.

Custom sliding expiration
Client wants custom sliding expiration per user (e.g. some will be 30 mins, some 2 weeks). I was going to add an event handler to CookieAuthenticationEvents.onSigningIn and modify the CookieSigningInContext.Propertie.ExpiresUtc property. Is that acceptable? or am i stuffing around with the boilerplate too much?

Force relogin every X days
With the sliding expiration, any claims will get regenerated when the GenerateClaimsAsync is rerun. (e.g. in the case of sliding expiration). So instead I was going to add a AbsoluteExpirationDate UTC to the CookieSigningInContext.Properties collection, and check it in OnValidatePrincipal. This seems to persist between requests and claims regeneration, but not sure if this is hacky?

Edit - looks like the SecurityStampValidator exposes OnRefreshingPrincipal which would allow me to access both old and new contexts, and copy the AbsoluteExpirationDate claim over. Are there any other instances a principal would get recreated? or is it just on login / security stamp expiry?


Two factor
The latest starter template has done away with the old SMS two factor code and are trying to push the Authenticator app. Client requires SMS though, not app.

I have just copied from the ASP.Net MVC (classic, not core) template which still uses GenerateTwoFactorTokenAsync to send via SMS, and then TwoFactorSignInAsync to log them in. The old SendCode/VerifyCode actions from the last template. Is this still acceptable?

Claim storage
So my understanding is all claims are serialised to a cookie, encrypted, and stored client side? They're then decrypted on each request and then converted to a ClaimsPrincipal. (The magic of the CookieAuthentiation middleware). This is secure, right? There's no chance a client can view or change their claims? Is there any other best practice instead? (i.e. send a "Session ID" for the cookie, and persist the claims server side via redis cache or similar)

Context usage and DI
I'm still stuck in the webforms mindset of wrapping all DB context operations in a single Using block. But now the new hotness is to have one context per request via DI?

So in my model I have the standard ApplicationUser. But I've extended this and added other entities (i.e. organisation)

These other entities are not eagerly loaded, and EF doesn't do lazy loading - what is the best way to load them? I have registered a ApplicationDbContext (which the UserManager and SigninManager uses). Am I okay to just grab this context via DI, and call context.Entry.Load? Or is there a risk these Managers might be using a different instance of the context?

Final edit - Security Stamp Validation interval
This system is going to be very low usage. So I was thinking of setting the validation interval to something low like 30 seconds or 1 minute. This way if a user changes a password on another PC, other sessions will be invalidated. But I can also use it to update the security stamp when I add/remove IPs to the whitelist, change the re-login interval, change the sliding expiry interval, etc.

Is that usable for a low-usage site, or is there a lot of overhead in regenerating a validating a security stamp, or regenerating a claims identity?


Thanks for the sanity check!

roflsaurus fucked around with this message at 08:40 on Jan 10, 2018

Funking Giblet
Jun 28, 2004

Jiglightful!

quote:

Claim storage
So my understanding is all claims are serialised to a cookie, encrypted, and stored client side? They're then decrypted on each request and then converted to a ClaimsPrincipal. (The magic of the CookieAuthentiation middleware). This is secure, right? There's no chance a client can view or change their claims? Is there any other best practice instead? (i.e. send a "Session ID" for the cookie, and persist the claims server side via redis cache or similar)
Thanks for the sanity check!

This depends on how you have it configured. You could have a reference token stored in a cookie which then calls to an IDP to retrieve claims on every request.

LongSack
Jan 17, 2003

I think I’m missing something with EF. I have a Title class which has a foreign key into the Genres table, so the the Title class has a navigation property of type Genre.

If I add a new Title, and then without disposing the context, try to delete it I get an exception “Adding a relationship with an entity which is in the Deleted State is not allowed”. If I exit the “Titles” window and come back I can delete it without a problem, so it seems like I’m missing something creating the new entity.

The only reliable fix I’ve found is - after adding the new title - to Dispose the context, create a new one, and to reload my ObservableCollection<Title>. This seems horribly inefficient. I have verified that after the SaveChanges() call the Genre navigation property is set correctly, so I’m not sure what’s going on.

For reference, the classes look like this (I’ve left out a bunch of extraneous stuff, for example all of the properties are observable, but that doesn’t seem relevant):
C# code:
class Genre
{
    int Id { get; set }
    string Description { get; set }
}

class Title
{
    int Id { get; set }
    string Name { get; set }
    int GenreId { get; set }      // Foreign key
}

Funking Giblet
Jun 28, 2004

Jiglightful!

LongSack posted:

I think I’m missing something with EF. I have a Title class which has a foreign key into the Genres table, so the the Title class has a navigation property of type Genre.

If I add a new Title, and then without disposing the context, try to delete it I get an exception “Adding a relationship with an entity which is in the Deleted State is not allowed”. If I exit the “Titles” window and come back I can delete it without a problem, so it seems like I’m missing something creating the new entity.

The only reliable fix I’ve found is - after adding the new title - to Dispose the context, create a new one, and to reload my ObservableCollection<Title>. This seems horribly inefficient. I have verified that after the SaveChanges() call the Genre navigation property is set correctly, so I’m not sure what’s going on.

Hrmm, I would always create a new context for a new operation, but you seem to have a different problem. You shouldn't need to reload everything, you have it already, just add the new entry to the Observable when SaveChanges is successful, dispose the context. If you need to delete, create a new context and on success, remove it from the Observable. You need decouple your Observable from the DBContext. your ViewModel should probably be a simple POCO, not an EF Entity. This should also make it easy to test

NihilCredo
Jun 6, 2011

iram omni possibili modo preme:
plus una illa te diffamabit, quam multæ virtutes commendabunt

Mr Shiny Pants posted:

Am I correct in assuming that do! reuses the current thread and starts an Async Workflow, while Async.Start fires off a new thread on the threadpool?

I'm not sure about the threading behaviour (which is mostly an implementation detail - the main time it matters is when doing GUI programming, and for that specific case you have `Async.StartImmediate` which exists to force the async operation to run on the GUI thread), but a much bigger difference between the two is that do! waits for the operation to complete before continuing the expression, while Async.Start does not.

E.g.

https://dotnetfiddle.net/lwaW2R

code:
async {
   printfn "1"
   do! async { Thread.Sleep 100; printfn "2" }
   printfn "3"
   Async.Start <| async { Thread.Sleep 100; printfn "4" }
   printfn "5"
}
|> Async.RunSynchronously
code:
1
2
3
5
4

NihilCredo fucked around with this message at 16:03 on Jan 11, 2018

Mr Shiny Pants
Nov 12, 2012

NihilCredo posted:

I'm not sure about the threading behaviour (which is mostly an implementation detail - the main time it matters is when doing GUI programming, and for that specific case you have `Async.StartImmediate` which exists to force the async operation to run on the GUI thread), but a much bigger difference between the two is that do! waits for the operation to complete before continuing the expression

This is what I was seeing, I thought Async.Start kicked it off on another thread, but it does not wait and do! does. Thanks for the clarification.

LongSack
Jan 17, 2003

Funking Giblet posted:

Hrmm, I would always create a new context for a new operation, but you seem to have a different problem. You shouldn't need to reload everything, you have it already, just add the new entry to the Observable when SaveChanges is successful, dispose the context. If you need to delete, create a new context and on success, remove it from the Observable. You need decouple your Observable from the DBContext. your ViewModel should probably be a simple POCO, not an EF Entity. This should also make it easy to test

My VMs are separate from the EF entities. I tried just disposing and recreating the context after SaveChanges(), but then got a different error message: "The object cannot be deleted because it was not found in the ObjectStateManager". That error is corrected by re-loading the ObservableCollection. I don't have any problems with entities that consist only of scalar properties, only on ones with navigation properties.

Here is one of the simplest ViewModels, used for managing Genders (i hope ~150 lines isn't too long). Note that the Gender class is the EF entity for the Genders table:
C# code:
    public class GenderViewModel : ViewModelBase
    {

        #region Properties

        private string _description;
        public string Description
        {
            get { return _description; }
            set { SetProperty(ref _description, value); }
        }

        private ObservableCollection<Gender> _genders;
        public ObservableCollection<Gender> Genders
        {
            get { return _genders; }
            set { SetProperty(ref _genders, value); }
        }

        private Gender _selectedGender;
        public Gender SelectedGender
        {
            get { return _selectedGender; }
            set { SetProperty(ref _selectedGender, value); }
        }

        private CharFolioEntities _context;

        #endregion

        #region Commands

        private RelayCommand _addCommand;
        public ICommand AddCommand
        {
            get
            {
                if (_addCommand == null)
                {
                    _addCommand = new RelayCommand(param => AddClick(), param => AddCanClick());
                }
                return _addCommand;
            }
        }

        private RelayCommand _deleteCommand;
        public ICommand DeleteCommand
        {
            get
            {
                if (_deleteCommand == null)
                {
                    _deleteCommand = new RelayCommand(param => DeleteClick(), param => GenderSelected());
                }
                return _deleteCommand;
            }
        }

        #endregion

        #region Command Methods

        private bool AddCanClick() => !string.IsNullOrEmpty(Description);

        private void AddClick()
        {
            if (string.IsNullOrEmpty(Description))
                return;
            Gender g = (from gen in _context.Genders where gen.Description.ToLower() == Description.ToLower() select gen).SingleOrDefault();
            if (g != null)
            {
                MessageBox.Show($"A Gender with description '{Description}' already exists.", "Duplicate Gender", MessageBoxButton.OK, MessageBoxImage.Stop);
                return;
            }
            g = new Gender
            {
                Description = Description.Capitalize()
            };
            _context.Genders.Add(g);
            try
            {
                _context.SaveChanges();
            }
            catch (Exception ex)
            {
                while (ex.InnerException != null)
                    ex = ex.InnerException;
                MessageBox.Show(ex.Message, "Failed to add Gender", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }
            int ix = 0;
            while (ix < Genders.Count && Genders[ix].Description.CompareTo(g.Description) < 0)
                ix++;
            Genders.Insert(ix, g);
            SelectedGender = g;
            Description = string.Empty;
            FocusRequested?.Invoke(this, EventArgs.Empty);
        }

        private bool GenderSelected() => SelectedGender != null;

        private void DeleteClick()
        {
            if (SelectedGender == null)
                return;
            _context.Genders.Remove(SelectedGender);
            try
            {
                _context.SaveChanges();
            }
            catch (Exception ex)
            {
                while (ex.InnerException != null)
                    ex = ex.InnerException;
                MessageBox.Show(ex.Message, "Failed to delete Gender", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }
            Genders.Remove(SelectedGender);
            SelectedGender = null;
            Description = string.Empty;
            FocusRequested?.Invoke(this, EventArgs.Empty);
        }

        #endregion

        #region Events

        public event EventHandler FocusRequested;

        #endregion

        public GenderViewModel()
        {
            _context = new CharFolioEntities();
            Genders = new ObservableCollection<Gender>(from g in _context.Genders orderby g.Description select g);
        }

        ~GenderViewModel()
        {
            _context?.Dispose();
        }
    }

Funking Giblet
Jun 28, 2004

Jiglightful!
Ok, this is what I was talking about, your view model is capturing a context, this is not correct at all and is going to cause a lot of issues (as it is already).

You are also relying on a destructor to dispose your context, this may never be called.

Let's try something small to give you an idea of how first to fix this.

In each of your methods, create a new context.

code:
private void DeleteClick()
        {
using(var context = new CharFolioEntities())
{
            if (SelectedGender == null)
                return;
            context.Genders.Remove(SelectedGender);
            try
            {
                context.SaveChanges();
            }
            catch (Exception ex)
            {
                while (ex.InnerException != null)
                    ex = ex.InnerException;
                MessageBox.Show(ex.Message, "Failed to delete Gender", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }
            Genders.Remove(SelectedGender);
            SelectedGender = null;
            Description = string.Empty;
            FocusRequested?.Invoke(this, EventArgs.Empty);
}
        }
And do similar to the Add.

Remove the _context variable.

Figure out a different way to Populate your observable, and maybe, try not using an EF entity, but a DTO of some sort.

This is not the way to do it, but should show some improvements, and show you the way a context should be used (short lived as possible) (I haven't much time now, but we can drill into a proper method of doing it)

LongSack
Jan 17, 2003


OK i'll play around with it, also thanks for the note about the destructor - i was not aware that they might not be called.

Edit: it seems to work well even using the Entity classes in my observables as long as i do something like this:
C# code:
        private void ClearDefaultClick()
        {
            if (SelectedProperty == null)
                return;
            using (var _context = new CharFolioEntities())
            {
                _context.CustomProperties.Attach(SelectedProperty);
                SelectedProperty.DefaultValue = string.Empty;
                try
                {
                    _context.SaveChanges();
                }
                catch (Exception ex)
                {
                    while (ex.InnerException != null)
                        ex = ex.InnerException;
                    MessageBox.Show(ex.Message, "Failed up update Custom Property", MessageBoxButton.OK, MessageBoxImage.Error);
                    return;
                }
            }
            Name = string.Empty;
            Default = string.Empty;
            FocusRequested?.Invoke(this, EventArgs.Empty);
        }
The Attach() method prevents exceptions about the entity not being in the ObjectStateManager.

LongSack fucked around with this message at 21:41 on Jan 12, 2018

bobua
Mar 23, 2003
I'd trade it all for just a little more.

This is probably a dumb question, but I'm pretty new to mvc\web work in general, and there is something I don't quite get.

I'm using a devexpress extension, their gridview. It supports paging, so I bind it to a list of a 1000 records and it happily pages them out for me. I also have to give it a callback actionresult.

Now, in all the tutorials and samples they just bind it to something on the model like 'model.GetEmployees()' and the callback also returns model.GetEmployees(); Is that actually happening the way it looks like it's happening or is there some smarter logic?

Like, when I click the page 2 link, is the controller pulling the same 1000 records again and sending them back to the view or is there some sort of logic I'm not seeing that's only checking for new records or something?

https://documentation.devexpress.com/AspNet/8998/ASP-NET-MVC-Extensions/Grid-View/Overview/Overview-GridView <--control in question

NihilCredo
Jun 6, 2011

iram omni possibili modo preme:
plus una illa te diffamabit, quam multæ virtutes commendabunt

Has anybody used Npgsql's LISTEN/NOTIFY support?

Am I supposed to only be able to handle notifications raised from the same connection? Because that cripples the feature, at least the way I wanted to use it (I had planned on having a background worker that did things whenever a row was added to a particular table, regardless of who or what added those rows). But the event doesn't seem to fire except when I manually call NOTIFY from the same connection where I called LISTEN.

Mr Shiny Pants
Nov 12, 2012

NihilCredo posted:

Has anybody used Npgsql's LISTEN/NOTIFY support?

Am I supposed to only be able to handle notifications raised from the same connection? Because that cripples the feature, at least the way I wanted to use it (I had planned on having a background worker that did things whenever a row was added to a particular table, regardless of who or what added those rows). But the event doesn't seem to fire except when I manually call NOTIFY from the same connection where I called LISTEN.

No, you can use a different connection, just keep one open that will listen for events.

This worked for me:

code:
let dbNotificationHandler (args:Npgsql.NpgsqlNotificationEventArgs) =
    printfn "%s" <| args.Condition;   


let conn = 
    let conn = new Npgsql.NpgsqlConnection(connString)
    conn.Open()
    conn

let stateHandler (args:Data.StateChangeEventArgs) =
    printfn "State changed from %s to %s" <| args.OriginalState.ToString() <| args.CurrentState.ToString()

let dbConn = 
    let cmd = new NpgsqlCommand()
    cmd.Connection <- conn
    cmd.CommandText <- "LISTEN delete;LISTEN update;LISTEN insert;"
    cmd.ExecuteNonQuery() |> ignore
    conn.StateChange.Add(stateHandler)
    conn.Notification.Add(dbNotificationHandler)
    conn

let runDbAsync (token:CancellationToken) =
    async {
        while not <| token.IsCancellationRequested
          printfn "Listening for PostgreSQL notifications"
            let timeout = not <| dbConn.Wait(TimeSpan.FromSeconds(5.0))
            if timeout then 
                keepAlive.ExecuteNonQuery() |> ignore 
                printfn "Sending keep alive"
        printfn "Stopping DB listener"
        dbConn.Close()
I pasted this from a FSX file, so it has some extra cruft, but it should work. You have to configure Postgres though, I needed to write some functions that encapsulated the update, insert and delete routines. I tested the above with an application that would write to Postgres using it's own DB connection.

The last part is broken I think, but you should send a keep alive to the server once in awhile.

Mr Shiny Pants fucked around with this message at 21:12 on Jan 12, 2018

NihilCredo
Jun 6, 2011

iram omni possibili modo preme:
plus una illa te diffamabit, quam multæ virtutes commendabunt

Thanks. Turns out the problem was dead-simple, as usual: channel names are case sensitive. :downs:

For the 'keep connection alive' requirement, I just enabled the KeepAlive option in the connection string, seems to work.

Mr Shiny Pants
Nov 12, 2012

NihilCredo posted:

Thanks. Turns out the problem was dead-simple, as usual: channel names are case sensitive. :downs:

For the 'keep connection alive' requirement, I just enabled the KeepAlive option in the connection string, seems to work.

Ah, Unix DB server of course. :)

There was some talk on the Npgsql github about the keepalive not working as it should when set in the connection string, maybe something to keep in mind.

Question for you, is Async Sleep with an Infinite timeout dumb to keep a thread running? I have a websocket server which I need to keep running and a client also.

NihilCredo
Jun 6, 2011

iram omni possibili modo preme:
plus una illa te diffamabit, quam multæ virtutes commendabunt

Mr Shiny Pants posted:

Ah, Unix DB server of course. :)

There was some talk on the Npgsql github about the keepalive not working as it should when set in the connection string, maybe something to keep in mind.

Question for you, is Async Sleep with an Infinite timeout dumb to keep a thread running? I have a websocket server which I need to keep running and a client also.

I'd look for a cleaner solution, to be honest. Whatever library you're using for websockets, if it doesn't totally suck, should provide an async method call to fetch the next message, so just await that. Kinda like an inbox.Receive when using a mailbox processor.

Or if you're balling hard and writing your own server, you could check out Suaves' implementation for ideas.

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

LongSack posted:

OK i'll play around with it, also thanks for the note about the destructor - i was not aware that they might not be called.

C# doesn't have destructors, it has finalizers. If you need deterministic disposal of unmanaged resources, use the IDisposable interface and wrap things in using blocks.

Mr Shiny Pants
Nov 12, 2012

NihilCredo posted:

I'd look for a cleaner solution, to be honest. Whatever library you're using for websockets, if it doesn't totally suck, should provide an async method call to fetch the next message, so just await that. Kinda like an inbox.Receive when using a mailbox processor.

Or if you're balling hard and writing your own server, you could check out Suaves' implementation for ideas.

I am using socketsharp, I'll take a look at Suave. Thanks.

Mr Shiny Pants
Nov 12, 2012
Maybe you guys know this, I haven't found a definitive answer. The Windows 10 IoT stuff has speech recognition, does this one need a network connection?

I don't want Cortana or any other cloud based stuff, just the regular Microsoft.Speech namespace would suffice.

Mr Shiny Pants
Nov 12, 2012
So I just installed, what I think was a Netstandard library from Nuget, and it installed a whole shitload of stuff. System.IO and other stuff. Why would it need those when I am using the full runtime?

EssOEss
Oct 23, 2006
128-bit approved
The reality of modern .NET packaging is a shitshow with many complications and design flaws. Every time I think I understand it and try to apply my knowledge, I find yet another stupid way for it to break. I think those packages are mostly empty or just have placeholder assemblies if you just target the full framework but I am not even sure. Would love to read a thorough explanation but I have never seen one that manages to cover even a decent portion of the angles.

What exactly was the package in question? Let's explore the data.

Adbot
ADBOT LOVES YOU

Mr Shiny Pants
Nov 12, 2012

EssOEss posted:

The reality of modern .NET packaging is a shitshow with many complications and design flaws. Every time I think I understand it and try to apply my knowledge, I find yet another stupid way for it to break. I think those packages are mostly empty or just have placeholder assemblies if you just target the full framework but I am not even sure. Would love to read a thorough explanation but I have never seen one that manages to cover even a decent portion of the angles.

What exactly was the package in question? Let's explore the data.

Well, I got it figured out, I had to set the dependency resolution in Nuget to "Ignore dependencies"..... yeah.

It works though. It is this one: https://github.com/Q42/Q42.HueApi

Edit: Seems like someone else was wondering the same: https://github.com/Q42/Q42.HueApi/issues/123

It looks like something from 1.0 netstandard what I gathered from SO link, should be fixed in 2.0.

I am really not starting to like the fragmentation.......

Edit 2: Now FSI works again with the DLL, it would error out because some System.Threading library would be missing if it installed all the dependencies. Yay.

Mr Shiny Pants fucked around with this message at 22:34 on Jan 14, 2018

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply