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
biznatchio
Mar 31, 2001


Buglord
If it helps you can create Exception classes really easy in Visual Studio by just typing "exception" and hitting tab twice. But that's just automating the boilerplate, not really eliminating it. I'll usually just throw an ApplicationException until I have a need to do catch filtering on that specific failure.

Adbot
ADBOT LOVES YOU

Opulent Ceremony
Feb 22, 2012
Can anyone recommend a vendor for generating PDFs? Nothing terribly fancy, just need to be able to take a byte array and treat it as a PDF, locate input fields on it and stamp data into them, flatten it for rendering a new PDF as a byte array with the data filled in.

We had been using iTextSharp but are now having licensing problems with it, and a replacement we've been working with appears to have unsolvable memory leak issues that become a problem at a high concurrent load.

Toast Museum
Dec 3, 2005

30% Iron Chef

Hammerite posted:

This post is apropos of nothing, I'm just getting some thoughts out of my head and onto the forum.

I like writing code in C#. Like any programming language in common use, it has its warts and design issues, but to badly quote Bjarne Stroustrup "there are 2 kinds of programming languages; the ones people complain about, and the ones no-one uses". C# has few enough design issues to make a tolerable language to work in.

A language that I was somewhat familiar with before I learned C# was Python, and I have occasionally found in C# that the lack of Python's so-called "class methods" was annoying to me. A "class method" in Python is distinct from an instance method in that it is static (in that it deals with the class object itself and not a specific instance of the class) but is part of the inheritance mechanism (so you can have the class method do something different depending on which class object you call it on).

In C# you can't do this, because static methods aren't part of the inheritance mechanism. The concept of inheritance is entirely incompatible with static methods, the way C# sees them. But this can sometimes be annoying, if you want to have an abstract base class with subclasses having characteristics specific to the subclass. For example, if you want to create a factory method that creates an instance of the subclass, and you want to define the factory method on the base class but be able to call it on the subclass and behave appropriately according to the subclass used to call it. For example, perhaps the maximum permitted value of some numeric parameter varies according to the subclass.

I had a revelation yesterday that this can be done in C# by way of an abstract generic base class and concrete subclasses that supply all of the type parameters. For example:

code:
...
(if desired, MyBaseClass's type parameter TSubclass can be further restricted e.g. given the class restriction. The MyConcreteClass constructor can of course call a base class constructor in the normal way if that is desirable)

Importantly, structs implementing ISubclassCharacteristics and ISubclassCreator should have no instance-dependent behaviour at all: all instances should behave identically to a default-constructed instance. This is not enforceable, but given that they exist to be default-constructed by MyBaseClass.MyFactoryMethod(), that seems harmless.

Now you can call the factory method as MyConcreteClass.MyFactoryMethod(), and get a MyConcreteClass instance back based on the parameters configured via MyConcreteClassCharacteristics.

This probably isn't very well explained because it's late here, and it's probably not that interesting to anyone else, but it's just something that never occurred to me before this weekend.

If you're up for explaining it in a little more detail, I think this might be useful to me, but I haven't got my head wrapped around it well enough to be sure. In poking at the problem described in my last post (thanks to Rocko Bonaparte for the feedback, while I'm thinking about it!), I ran into related issues (I think) involving how inheritance and reflection interact. Specifically, I was disappointed to learn that if you're calling an inherited method from a subclass, if you're using the default implementation, then reflection on that method will return the type where it's defined, not where it's being called. I'm just about positive that making Subclass.MethodDefinedInBaseClassThatReturnsSubclassReflectionInfo() work will not be worth the added complexity, but the whole project is mostly educational anyway, so I'm still interested in what's down that rabbit hole.

brap
Aug 23, 2004

Grimey Drawer
I feel like declaring your own exception types should not be something you need to do on a frequent basis.

ThePeavstenator
Dec 18, 2012

:burger::burger::burger::burger::burger:

Establish the Buns

:burger::burger::burger::burger::burger:
Custom exception types can be good in situations where you can identify what’s wrong and want to be specific, but can’t or shouldn’t handle that situation in the layer of code you’re on.

For example, some method call reads Foo data from a DB and then does some processing on it and then returns the result. Foo data has FieldA and FieldB, and only A or B should have a value, not both. If you do a check in the method to make sure FieldA and FieldB aren’t both set before processing each piece of Foo data, you can define a more specific custom exception to throw in cases where FieldA and FieldB are both set. Then when your custom MutuallyExclusiveFooFieldException or whatever you call it shows up in logs it’s easy to diagnose the issue and also correlate/quantify how often that happens. This also has the benefit of upstream callers that might know how to resolve that specific issue being able to catch that specific exception.

This is kind of a subjective design decision though and there are other valid ways to get easy to debug telemetry and conditional error handling.

epswing
Nov 4, 2003

Soiled Meat

brap posted:

I feel like declaring your own exception types should not be something you need to do on a frequent basis.

I'm not sure if this is good/bad practice, but I tend to only declare my own exception types when I have a try/catch with which to use them (which is infrequent). Otherwise what's the point? Sounds like a lot of boilerplate, as mentioned, what are the advantages of throwing custom exception types (especially if you're not using them in a catch block)?

Edit: ^^^gotcha, sure, I suppose there is a logging benefit. I've found the call stack and the exception message is usually enough to tell the story, though.

epswing fucked around with this message at 06:35 on Mar 9, 2021

Funking Giblet
Jun 28, 2004

Jiglightful!
Improved pattern matching makes generic exceptions easier to work with.

LongSack
Jan 17, 2003

In this specific case for my application, there are three assemblies running - 2 APIs (one for identity, one for the "store") and the web application. So even when I throw an exception in one of the APIs or in one of the class libraries supporting the APIs, I still need a way to communicate that error information back to the web application. Simply throwing an exception won't do it. That's why I'm handling it in the manner that I am, with the Either class.

I throw exceptions all the time in library code, or in monolithic applications like desktop. ArgumentNullException, ArgumentOutOfRangeException, InvalidOperationException, whatever is appropriate. I also do occasionally throw custom exceptions when I need to communicate more than just an error message.

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

LongSack posted:

In this specific case for my application, there are three assemblies running - 2 APIs (one for identity, one for the "store") and the web application. So even when I throw an exception in one of the APIs or in one of the class libraries supporting the APIs, I still need a way to communicate that error information back to the web application. Simply throwing an exception won't do it. That's why I'm handling it in the manner that I am, with the Either class.

I throw exceptions all the time in library code, or in monolithic applications like desktop. ArgumentNullException, ArgumentOutOfRangeException, InvalidOperationException, whatever is appropriate. I also do occasionally throw custom exceptions when I need to communicate more than just an error message.

APIs should indicate success or failure by using standard HTTP status codes. Specific exception details are an implementation detail that consumers shouldn't need to know about.

Think of it like this:

Web: API, do this thing.
API: Sorry, I can't. You probably don't need to know the specifics.
Web: Okay. I will let the person using me know that something went wrong. Or I'll retry if it's a transient problem.

All of these application pieces should be instrumented and sending telemetry data somewhere. I like Azure App Insights/Log Analytics, but I'm an Azure shill so :shrug:

New Yorp New Yorp fucked around with this message at 16:47 on Mar 9, 2021

insta
Jan 28, 2009

Funking Giblet posted:

Improved pattern matching makes generic exceptions easier to work with.

The language designers had to add this because everybody involved is garbage at exceptions and exception hierarchies. It's literally 90 seconds to add a new exception type yet somehow that's too much work. Throwing 'new Exception ($"Unable to load {id} from database")' is apparently way better than "new MissingDataException(id)" for reasons I'll never understand.

Bonus if you catch the actual exception and throw your own Exception (not even any subtype!) without including any of the original context.

Or double bonus if you catch(Exception e) { throw e; } and blow away my stack trace when I'm trying to debug logs from prod.

Or super gently caress you if you catch(Exception up) { throw up; /* he he he */ } like the actual code I found, complete with "he he he" in a comment.

Hammerite
Mar 9, 2007

And you don't remember what I said here, either, but it was pompous and stupid.
Jade Ear Joe

Toast Museum posted:

If you're up for explaining it in a little more detail, I think this might be useful to me, but I haven't got my head wrapped around it well enough to be sure. In poking at the problem described in my last post (thanks to Rocko Bonaparte for the feedback, while I'm thinking about it!), I ran into related issues (I think) involving how inheritance and reflection interact. Specifically, I was disappointed to learn that if you're calling an inherited method from a subclass, if you're using the default implementation, then reflection on that method will return the type where it's defined, not where it's being called. I'm just about positive that making Subclass.MethodDefinedInBaseClassThatReturnsSubclassReflectionInfo() work will not be worth the added complexity, but the whole project is mostly educational anyway, so I'm still interested in what's down that rabbit hole.

I don't know. Maybe?

I'm not sure how this interacts with reflection. I'm not doing any reflection in the implementation of this I have. Or are you saying that this might be how you would avoid reflection?

If you give more details of what your subclasses represent and what varies in each one I might have more idea.

I would post the code that I was actually working on when I had this idea, but I haven't gone to the trouble of adding a licence yet. (It is a personal project and I already know I am going to GPL it but I haven't done the mechanical work of adding the licence boilerplate yet. I am lazy)

LongSack
Jan 17, 2003

New Yorp New Yorp posted:

APIs should indicate success or failure by using standard HTTP status codes.

They do. They return 200, 201 and 400 (and 500 if I mess something up in development, like forgetting to set the BasePath in my UriHelper class).

And I can’t just say “something went wrong”, I need to tell the end user what went wrong. For example, when creating a new Category, the CategoryService in the portal will check for duplicate names. The API will also check (never trust data from the user). The Data Access Layer service will also check.

If I didn’t do those checks, EF would throw a badly-worded exception. That message, I agree, the user should never see, which is why each of the above checks returns a message that says “Duplicate Category Name” which ends up in the ModelState.

beuges
Jul 4, 2005
fluffy bunny butterfly broomstick

LongSack posted:

They do. They return 200, 201 and 400 (and 500 if I mess something up in development, like forgetting to set the BasePath in my UriHelper class).

And I can’t just say “something went wrong”, I need to tell the end user what went wrong. For example, when creating a new Category, the CategoryService in the portal will check for duplicate names. The API will also check (never trust data from the user). The Data Access Layer service will also check.

If I didn’t do those checks, EF would throw a badly-worded exception. That message, I agree, the user should never see, which is why each of the above checks returns a message that says “Duplicate Category Name” which ends up in the ModelState.

You can return a http 400 with the body “Duplicate Category Name”. You can write a middleware in your api to catch an custom created ApiException which has a status code and message, and return the appropriate response. You can then wrap calls to your api to check the status code and display the error appropriately, or bubble it back if you’re not yet at the customer browser level.

LongSack
Jan 17, 2003

beuges posted:

You can return a http 400 with the body “Duplicate Category Name”. You can write a middleware in your api to catch an custom created ApiException which has a status code and message, and return the appropriate response. You can then wrap calls to your api to check the status code and display the error appropriately, or bubble it back if you’re not yet at the customer browser level.

That's exactly what I'm doing. The store APIs return 200/201 on success, and 400 on failure. The success payload varies based on the request, but the 400 payload is always an instance of the ApiException class. The portal services which call the APIs are using a generic "Either" class to retrieve the results based on RichardA's response further up in this thread.

biznatchio
Mar 31, 2001


Buglord

Funking Giblet posted:

Improved pattern matching makes generic exceptions easier to work with.

Yes, 1000%. For inter-project exceptions they more or less entirely eliminate the need for custom exception types.

But if you're designing a reusable library, I'd still recommend creating and throwing your own exception types rather than saddling the library's consumers with 1) needing to know what pattern they should look for to catch the specific failure they care about catching; and 2) accidentally permanently tying yourself to having to maintain that your exception continues to match that arbitrary ad hoc pattern in future releases of your library. A (lowercase t) type is a type, regardless of whether it's a (uppercase T) Type or not; and if you make it a Type your life will be easier in the long run at the minor expense of 30 seconds of work. It's always better to be as explicit as possible.


edit: and I also agree that there's a special place in hell for anyone who's ever written throw e; to rethrow a caught exception. I can't believe the compiler doesn't even at least flag it as a warning, because if you ask me, it should be no less than a compile error.

biznatchio fucked around with this message at 16:53 on Mar 10, 2021

Toast Museum
Dec 3, 2005

30% Iron Chef

Hammerite posted:

I don't know. Maybe?

I'm not sure how this interacts with reflection. I'm not doing any reflection in the implementation of this I have. Or are you saying that this might be how you would avoid reflection?

If you give more details of what your subclasses represent and what varies in each one I might have more idea.

I would post the code that I was actually working on when I had this idea, but I haven't gone to the trouble of adding a licence yet. (It is a personal project and I already know I am going to GPL it but I haven't done the mechanical work of adding the licence boilerplate yet. I am lazy)

It's been a week or so since I last took a crack at it, so I'm fuzzy on how the reflection-related issues and static-related issues overlapped. The situation I've run into is pretty much entirely because I don't want to do an easy-but-clunky thing. Mostly as a way to learn C#, I'm working on a project that interacts with an XML-RPC API. This API has some qualities that make it a little frustrating to deal with, including:
  1. The API doesn't have any methods like api.getUser that directly return a representation of a business object.
  2. It does have methods like api.getUserProperties(string user, string[] properties) and api.setUserProperties(string user, string[][] propertiesAndValues), so the next-best thing is to feed one of these methods a list of all properties the API considers gettable/settable for that type.
  3. These methods all return string[], where each item in the array is a string representation of a property's value, in the order that the properties were listed when you called the method.
  4. Many of the property names the API uses are either discouraged or disallowed in C#, so when I'm making classes to represent these business objects, I need to call the members something different.

Since these we're talking about a couple dozen properties on some of these objects, I really don't like the idea of doing something like
code:
User GetUser(string name)
{
    var user = new user();
    var response = api.getUserProperties(name, new string[] {"prop.0","prop.1", ... "prop.25"});
    user.Prop0 = response[0];
    user.Prop1 = response[1];
    ...
    user.Prop2 = response[25];
    return user;
}
I mean, it works, but it's tedious to write and feels like it'd be a pain to update. The alternative I've come up with is to use a custom attribute to describe how the C# class members relate to the API, e.g.
code:
public class User
{
    [ApiProperty(name: "account-selection.mode", canGet: true, canSet: false)]
    internal string _accountSelectionMode;
    public string AccountSelectionMode
    {
        get => _accountSelectionMode;
        set => _accountSelectionMode = value;
    }

    [ApiProperty(name: "balance", canGet: true, canSet: true)]
    internal string _balance;
    public double Balance
    {
        get => double.Parse(_balance);
        set => _balance = value.ToString();
    }

    // etc.
}
Rather than maintain hardcoded lists of gettable and settable properties for each class, I can use reflection to produce those lists based on the attributes applied to the backing fields:
code:
static List<string> GetApiGettableProperties(Type t)
{
    return
        (
        from field in t.GetFields()
        where field.IsDefined(typeof(ApiPropertyAttribute))
        where field.GetCustomAttribute<ApiPropertyAttribute>().CanGet == true
        select field.GetCustomAttribute<ApiPropertyAttribute>().Name
        ).ToList();
}
For now, I've got that method sitting in a utility class and call it from a factory method when instantiating one of these business object classes, but doing the reflection repeatedly seems silly when the list of members isn't going to change from one instance to the next. What I was hoping I could do is create a BusinessObject base class, inherited by each class representing a business object, with static readonly ApiGettableProperties and ApiSettableProperties fields populated by doing the relevant reflection once for each class. Unfortunately, since the code to generate those lists was defined in the base class, reflection returned information about the base class, not the inheriting classes. Like I said at the top, it's been a minute since I messed with this issue, so I'm probably being unclear about the roadblocks I hit.

Edit: fixed an error in the sample code

Toast Museum fucked around with this message at 01:40 on Mar 11, 2021

WorkerThread
Feb 15, 2012

Toast Museum posted:

It's been a week or so since I last took a crack at it, so I'm fuzzy on how the reflection-related issues and static-related issues overlapped. The situation I've run into is pretty much entirely because I don't want to do an easy-but-clunky thing. Mostly as a way to learn C#, I'm working on a project that interacts with an XML-RPC API. This API has some qualities that make it a little frustrating to deal with, including:
  1. The API doesn't have any methods like api.getUser that directly return a representation of a business object.
  2. It does have methods like api.getUserProperties(string user, string[] properties) and api.setUserProperties(string user, string[][] propertiesAndValues), so the next-best thing is to feed one of these methods a list of all properties the API considers gettable/settable for that type.
  3. These methods all return string[], where each item in the array is a string representation of a property's value, in the order that the properties were listed when you called the method.
  4. Many of the property names the API uses are either discouraged or disallowed in C#, so when I'm making classes to represent these business objects, I need to call the members something different.

Since these we're talking about a couple dozen properties on some of these objects, I really don't like the idea of doing something like
code:
User GetUser(string name)
{
    var user = new user();
    var response = api.getUserProperties(name, new string[] {"prop.0","prop.1", ... "prop.25"});
    user.Prop0 = response[0];
    user.Prop1 = response[1];
    ...
    user.Prop2 = response[25];
    return user;
}
I mean, it works, but it's tedious to write and feels like it'd be a pain to update. The alternative I've come up with is to use a custom attribute to describe how the C# class members relate to the API, e.g.
code:
public class User
{
    [ApiProperty(name: "account-selection.mode", canGet: true, canSet: false)]
    internal string _accountSelectionMode;
    public string AccountSelectionMode
    {
        get => _accountSelectionMode;
        set => _accountSelectionMode = value;
    }

    [ApiProperty(name: "balance", canGet: true, canSet: true)]
    internal string _balance;
    public double Balance
    {
        get => double.Parse(_balance);
        set => _balance = value.ToString();
    }

    // etc.
}
Rather than maintain hardcoded lists of gettable and settable properties for each class, I can use reflection to produce those lists based on the attributes applied to the backing fields:
code:
static List<string> GetApiGettableProperties(Type t)
{
    return
        (
        from field in t.GetType().GetFields()
        where field.IsDefined(typeof(ApiPropertyAttribute))
        where field.GetCustomAttribute<ApiPropertyAttribute>().CanGet == true
        select field.GetCustomAttribute<ApiPropertyAttribute>().Name
        ).ToList();
}
For now, I've got that method sitting in a utility class and call it from a factory method when instantiating one of these business object classes, but doing the reflection repeatedly seems silly when the list of members isn't going to change from one instance to the next. What I was hoping I could do is create a BusinessObject base class, inherited by each class representing a business object, with static readonly ApiGettableProperties and ApiSettableProperties fields populated by doing the relevant reflection once for each class. Unfortunately, since the code to generate those lists was defined in the base class, reflection returned information about the base class, not the inheriting classes. Like I said at the top, it's been a minute since I messed with this issue, so I'm probably being unclear about the roadblocks I hit.

Why not build a component that handles doing that field conversion for you, rather than making it a responsibility of the objects themselves. Then you can cache the result of the reflection per type inside that component if you want to.

Also, is the transformation from the rpc spec to C# class rote or does it require tender loving care from you? Cause if it's pretty mechanical, you could look at Roslyn source generators to do it for you directly from whatever schema you're using.

NihilCredo
Jun 6, 2011

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

beuges posted:

You can return a http 400 with the body “Duplicate Category Name”.

Bikeshed alert:

I like to use the 409 Conflict for duplicate requests. It's good not to dump it into the generic 400 error code because there was nothing wrong with the request itself, it just didn't mesh with the state of the data store at the time.

It also works well with idempotency (which I try hard to bake into all my APIs), because then the client can just retry the requests and accept a 409 as valid ( = an earlier request went through, the client just didn't get the answer).

There's a few other codes that might be suitable for this, but whatever you choose, the important thing IMO is to use 400 and 500 as sparingly as possible, since they're the generic 'wtf come get a human to read the message' codes.

salisbury shake
Dec 27, 2011
If I'm a Linux user who will never use the .NET platform on Windows, how painful is it to use F# for application, system or web development?

mystes
May 31, 2006

salisbury shake posted:

If I'm a Linux user who will never use the .NET platform on Windows, how painful is it to use F# for application, system or web development?
Not at all

except when the vscode plugin decides to randomly break lol

GI_Clutch
Aug 22, 2000

by Fluffdaddy
Dinosaur Gum
Does anyone else ever look at this thread's title and ask why the method isn't named "GetGoodPostsAsync()"?

Drastic Actions
Apr 7, 2009

FUCK YOU!
GET PUMPED!
Nap Ghost

salisbury shake posted:

If I'm a Linux user who will never use the .NET platform on Windows, how painful is it to use F# for application, system or web development?

VSCode and Omnisharp is fine. Rider is also pretty good for F#.

New Yorp New Yorp
Jul 18, 2003

Only in Kenya.
Pillbug

GI_Clutch posted:

Does anyone else ever look at this thread's title and ask why the method isn't named "GetGoodPostsAsync()"?

I considered it. It's probably time for a new thread anyway. I'm not making it though.

insta
Jan 28, 2009

salisbury shake posted:

If I'm a Linux user who will never use the .NET platform on Windows, how painful is it to use F# for application, system or web development?

F# is as platform-agnostic as C# is. The underlying library also got very cross-platform in Core, and continues to do so.

You as the developer will be the weak link, doing things like concatenating paths together instead of using Path.Combine.

VSCode and 'dotnet' will do a lot more than you'd think they will!

redleader
Aug 18, 2005

Engage according to operational parameters
the real tricky cross-platform bits are around locales and time zones and the like imo

Mr Shiny Pants
Nov 12, 2012
And any stuff that needs to interface with Windows stuff like NTLM.

Otherwise it is actually pretty good.

Hammerite
Mar 9, 2007

And you don't remember what I said here, either, but it was pompous and stupid.
Jade Ear Joe

GI_Clutch posted:

Does anyone else ever look at this thread's title and ask why the method isn't named "GetGoodPostsAsync()"?

.NET Megathread 5.0: GetGoodPostsAsync().ConfigureAwait(false);

NihilCredo
Jun 6, 2011

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

insta posted:

You as the developer will be the weak link, doing things like concatenating paths together instead of using Path.Combine.

<bell curve meme>

low tail: concatenate paths with "\"

middle of the bell : concatenate paths by using Path.Combine

high tail: concatenate paths with "/", because it works on both Windows and Linux, multiple consecutive slashes have no effect, and it doesn't do that super annoying thing Path.Combine does where if a segment starts with "/" it reads it as an absolute path and it deletes every segment before it

Ola
Jul 19, 2004

Hammerite posted:

.NET Megathread 5.0: GetGoodPostsAsync().ConfigureAwait(false);

.NET Megathread 5.0: _goodPostsRepository.GetGoodPostsAsync().ConfigureAwait(false);

NihilCredo
Jun 6, 2011

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

Ola posted:

.NET Megathread 5.0: _goodPostsRepository.GetGoodPostsAsync().ConfigureAwait(false);

.NET Megathread 5.0: return! goodPostsRepository |> GoodPosts.getAsync

WorkerThread
Feb 15, 2012

NihilCredo posted:

.NET Megathread 5.0: return! goodPostsRepository |> GoodPosts.getAsync

:hmmyes:

Toast Museum
Dec 3, 2005

30% Iron Chef

WorkerThread posted:

Why not build a component that handles doing that field conversion for you, rather than making it a responsibility of the objects themselves. Then you can cache the result of the reflection per type inside that component if you want to.

No real reason, I guess. Keeping information about each class within that class just felt like the tidiest option. Happily, I think I've figured it out. I was able to achieve the effect I was going for by making the base class generic:

code:
public abstract class BusinessObject<T>
{
    public static readonly ReadOnlyCollection<string> ApiGettableProperties = (
            from field in typeof(T).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
            where field.IsDefined(typeof(ApiPropertyAttribute))
            where field.GetCustomAttribute<ApiPropertyAttribute>().CanGet == true
            select field.GetCustomAttribute<ApiPropertyAttribute>().Name
            ).ToList().AsReadOnly();

    public static readonly ReadOnlyCollection<string> ApiSettableProperties = (
            from field in typeof(T).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
            where field.IsDefined(typeof(ApiPropertyAttribute))
            where field.GetCustomAttribute<ApiPropertyAttribute>().CanSet == true
            select field.GetCustomAttribute<ApiPropertyAttribute>().Name
            ).ToList().AsReadOnly();
}

public class User : BusinessObject<User>
{
    // ...
}
Each subclass winds up with a couple of static members containing collections that are correctly based on reflection on the subclass.

WorkerThread posted:

Also, is the transformation from the rpc spec to C# class rote or does it require tender loving care from you? Cause if it's pretty mechanical, you could look at Roslyn source generators to do it for you directly from whatever schema you're using.

I've been focusing on one of the business object classes while I sort out my approach, but the logic for the C# class members is mostly pretty straightforward. The API always handles objects' properties as strings, so for the C# classes, I stick those strings in backing fields, with getters parsing the strings into more useful types (usually bool? or double?, with a DateTime or two for flavor), and setters converting back to string. In a couple of cases, the value of a bool has to be flipped to account for the C# class using affirmative member names where the API uses negative names.

My familiarity with Roslyn is basically "I've heard that name," but I'm up for checking it out. Where's a good place to start?

mystes
May 31, 2006

NihilCredo posted:

high tail: concatenate paths with "/", because it works on both Windows and Linux, multiple consecutive slashes have no effect, and it doesn't do that super annoying thing Path.Combine does where if a segment starts with "/" it reads it as an absolute path and it deletes every segment before it
Huh, no poo poo.

Mr Shiny Pants
Nov 12, 2012

mystes posted:

Huh, no poo poo.

Yep. It really sucks.

epswing
Nov 4, 2003

Soiled Meat

NihilCredo posted:

that super annoying thing Path.Combine does where if a segment starts with "/" it reads it as an absolute path and it deletes every segment before it

Whaaat?

I had to try it to believe it but... drat.

mystes
May 31, 2006

Mr Shiny Pants posted:

Yep. It really sucks.
I meant forward slashes working on windows. I would have anticipated the Path.Combine thing with segments starting with "/" as one possible behavior it could have.

Hammerite
Mar 9, 2007

And you don't remember what I said here, either, but it was pompous and stupid.
Jade Ear Joe

Toast Museum posted:

No real reason, I guess. Keeping information about each class within that class just felt like the tidiest option. Happily, I think I've figured it out. I was able to achieve the effect I was going for by making the base class generic:

Yeah, that seems like a decent way to handle it. It's not quite the same thing as what I was talking about in my post but it's not a million miles away. Glad you managed to something you are happy with.

(I have heard of Roslyn but I don't know anything about it)

SirViver
Oct 22, 2008
Path.Combine is a real easy way to introduce a huge security issue to your web app when you directly feed it user input, thinking you're safe because you don't realize how it actually behaves (and your app isn't installed in a tightly access-controlled manner). Oh yeah look I'm dynamically serving you files from a per-user sub-directory of some mapped network drive base path. Since I'm always starting with the base path I'm fine right? Strange, why is this user requesting "C:\inetpub\wwwroot\derp-app\web.config" and why the hell is it working??

It's not just that relative paths are resolved, e.g. "X:\base\path" + "..\..\rootfile.txt" => "X:\rootfile.txt", but also if any of the Path.Combine parameters is rooted, all stuff that came before it is ignored outright, like "X:\sandbox" + "user" + "upload" + "C:\secret.txt" => "C:\secret.txt".

Sure it's not difficult to safeguard against this (check if the resolved combined path still StartsWith() your safe root path afterwards), but I bet there's tons of exploits out there just because it's really not obvious it would behave that way.

WorkerThread
Feb 15, 2012

Toast Museum posted:

I've been focusing on one of the business object classes while I sort out my approach, but the logic for the C# class members is mostly pretty straightforward. The API always handles objects' properties as strings, so for the C# classes, I stick those strings in backing fields, with getters parsing the strings into more useful types (usually bool? or double?, with a DateTime or two for flavor), and setters converting back to string. In a couple of cases, the value of a bool has to be flipped to account for the C# class using affirmative member names where the API uses negative names.

My familiarity with Roslyn is basically "I've heard that name," but I'm up for checking it out. Where's a good place to start?

Sorry, on the road and phone posting this afternoon; https://devblogs.microsoft.com/dotnet/introducing-c-source-generators/ is where I started. It's probably still a bit too rough around the edges for production, but something I'm playing around with is similar to what you're describing-- writing a generator to automatically create C# clients for swagger files included in the solution/compilation.

Adbot
ADBOT LOVES YOU

nielsm
Jun 1, 2009



Why is my list not sorted... I must be missing something obvious here.
WPF on Framework 4.6.1

C# code:
        private void InsertLogFileInList(LogFileData data, ObservableCollection<LogFileData> collection)
        {
            bool inserted = false;
            for (int pos = 0; pos < collection.Count; pos++)
            {
                if (collection[pos].MachineName == "(sim)") continue;
                if (collection[pos].DateSortable < data.DateSortable)
                {
                    Dispatcher.Invoke(() => collection.Insert(pos, data));
                    inserted = true;
                    break;
                }
            }
            if (!inserted) Dispatcher.Invoke(() => collection.Insert(0, data));
        }
All items in the ObservableCollection are added via this method, except a single one which is the "(sim)" one, which is appended. All items except for the "(sim)" are discovered and added in the same task.
The actual result is that I get several individually sorted sequences of items in the list.

I'd consider putting a SortDescription on the ListBox.Items, but can't figure out from the docs how to handle the (sim) item, which needs to be last.


Edit: Never mind, I figured out what I'm doing wrong. It's the if (!inserted) at the end, that should be appending the item rather than prepending it.

nielsm fucked around with this message at 10:22 on Mar 16, 2021

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