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
Mr Shiny Pants
Nov 12, 2012

RICHUNCLEPENNYBAGS posted:

Yeah but the XML schema is stupid as hell if you actually look at files.

And really really complicated. My colleague tried to create documents using Word ML. drat..... He got it working in the end but it feels really over-engineered.

Adbot
ADBOT LOVES YOU

RICHUNCLEPENNYBAGS
Dec 21, 2010

Mr Shiny Pants posted:

And really really complicated. My colleague tried to create documents using Word ML. drat..... He got it working in the end but it feels really over-engineered.

That's still probably easier than trying to read arbitrary Word files. Ugh.

The worst part of it is mail merge stuff is mostly just text and not part of the XML schema and can even end up being split by the various tags (I mean besides the field code markers, which are always an XML element unto themselves).

I'm sure the stuff other than Word is even more of a nightmare though.

Forgall
Oct 16, 2012

by Azathoth
I want to try out some C# code parsing/manipulation/generation and I'm trying to decide between using NRefactory or Roslyn. They both seem to largely lack documentation at the moment (or maybe I'm looking in the wrong places?), plus Roslyn's API seems to be in a flux so most examples I can find no longer work, and its pre-release license is kind of weird and restrictive. On the other hand it's supported by Microsoft so it's likely going to be more complete and stable once it's out. Any thoughts?

RICHUNCLEPENNYBAGS
Dec 21, 2010

Forgall posted:

I want to try out some C# code parsing/manipulation/generation and I'm trying to decide between using NRefactory or Roslyn. They both seem to largely lack documentation at the moment (or maybe I'm looking in the wrong places?), plus Roslyn's API seems to be in a flux so most examples I can find no longer work, and its pre-release license is kind of weird and restrictive. On the other hand it's supported by Microsoft so it's likely going to be more complete and stable once it's out. Any thoughts?

Well, depends what your horizon is like but I imagine Roslyn will end up with more robust support.

There are a number of other techniques you can use for metaprogramming (reflection is obvious, but also dynamic proxies, IL emission, LINQ expressions, etc.).

wwb
Aug 17, 2004

idontcare posted:

Regardless if it's a terrible idea or not, I need to do it. Is it possible?

It is a terrible idea. But users authenticated via basic auth do appear in a http header and you should be able to do url rewrites based on that.

But this is loving retarded and you are probably solving a problem in an rear end-backwards way.

sausage king of Chicago
Jun 13, 2001

Bognar posted:

You might be able to do it with a custom HTTP handler. However, can you explain why you need to it?

So we license software from another company and the license expires at the end of the year. It generates an XML feed that we use to provide content to our clients. We wrote a new system that will take the place of this software and generates an identical XML feed. Our clients use importers that access the feed and pull in the content. The importers are all pointing at the servers with the old software on it.

I have access to the servers, I don't have access to the code. The url of the client's feeds are in the form http://username:pass(at)ourserver.com/folder. There are no unique identifiers in the url other than the username/pass. The username/pass are keys generated by the old system that I have access to and won't change and the client's aren't able to change.

My boss wants to slowly transition clients from the old system to our new system, and wants to do it one client at a time. He wants the transition to be smooth and not have the client's need to do anything on their end. What I posted about before - redirecting the request to http://username:pass to http://username:pass based on username - was the proposed solution to the problem. It's a temporary solution so not all client's are switched over at once.

sausage king of Chicago fucked around with this message at 16:02 on Oct 31, 2014

wwb
Aug 17, 2004

Ok, still a bit of a retarded architecture but you should be able to do url rewrites based on the HTTP_REMOTE_USER server variable (that you should not be trusting).

Perhaps a better way would be to send everyone to the new URL and then have that redirect folks to the old or new system based on some data lookups -- maintaining that url rewrite file will be a bitch.

raminasi
Jan 25, 2005

a last drink with no ice

Mr Shiny Pants posted:

Reading it back I don't know what I meant :) This stuff is all new to me.

Edit: Thinking about it some more:

What is different from something like C# is that methods only execute when they get called. in F# a let binding executes all the functions that comprise it during execution without needing to be called explicitly in program flow.

I don't know, can't really explain it I guess.

They do not get executed during compilation right?

F# will cache the values of functions with no parameters. That's the closest thing I can think of to what you might be talking about. I don't know what "a let binding executes all the functions that comprise it during execution without needing to be called explicitly in program flow" means.

sausage king of Chicago
Jun 13, 2001

wwb posted:

Ok, still a bit of a retarded architecture but you should be able to do url rewrites based on the HTTP_REMOTE_USER server variable (that you should not be trusting).

Perhaps a better way would be to send everyone to the new URL and then have that redirect folks to the old or new system based on some data lookups -- maintaining that url rewrite file will be a bitch.

Nice, that's a good idea that I hadn't thought of. I'll bring it up. Thanks a lot.

Mr Shiny Pants
Nov 12, 2012

GrumpyDoctor posted:

F# will cache the values of functions with no parameters. That's the closest thing I can think of to what you might be talking about. I don't know what "a let binding executes all the functions that comprise it during execution without needing to be called explicitly in program flow" means.

That is what I meant. I am used to instantiating classes and the like with C# before you can use them. F# will evaluate let bindings and instantiate stuff during program start. Just a different way of programming I guess.

At least for me, a different way of thinking about things.

Thanks for clearing up the caching.

raminasi
Jan 25, 2005

a last drink with no ice

Mr Shiny Pants posted:

That is what I meant. I am used to instantiating classes and the like with C# before you can use them. F# will evaluate let bindings and instantiate stuff during program start. Just a different way of programming I guess.

At least for me, a different way of thinking about things.

Thanks for clearing up the caching.

Well, I still really don't have any idea what you're talking about, but I guess it's working for you.

ljw1004
Jan 18, 2005

rum

Mr Shiny Pants posted:

That is what I meant. I am used to instantiating classes and the like with C# before you can use them. F# will evaluate let bindings and instantiate stuff during program start. Just a different way of programming I guess.
At least for me, a different way of thinking about things.

That sounds kind of odd...

CLASSES
code:
class C {
  string s = System.DateTime.Now.ToString();
}

type C() = 
    let s = System.DateTime.Now.ToString()
Both are equivalent. In both cases, when you instantiate "C", then that instance of "C" makes the call to DateTime.Now.ToString(), and stores the value in an instance-specific field.



GLOBALS
code:
class Program {
   static string t = System.DateTime.Now.ToString();
}

let t = System.DateTime.Now.ToString();
Both are pretty much equivalent. F# has a built-in notion of global variables. The equivalent in C# is to put them inside classes. The F# code is executed at the moment it gets to the statement. The C# code is executed prior to the first time you mention the containing class in any way whatsoever.



It's good that you have a new way of thinking about program structure. Just be aware that this style of program structure is equally well achievable in both C#/VB and F#.

Mr Shiny Pants
Nov 12, 2012

GrumpyDoctor posted:

Well, I still really don't have any idea what you're talking about, but I guess it's working for you.

Nevermind, I guess I suck at explaining. :) The Access Violation cleared itself up BTW. No clue why.

Mr Shiny Pants fucked around with this message at 06:39 on Nov 3, 2014

brap
Aug 23, 2004

Grimey Drawer
Honestly I'm mostly just interested in seeing the good parts of F# make it to C#-- pattern matching, discriminated unions and all that jazz.

raminasi
Jan 25, 2005

a last drink with no ice
Here I am patiently waiting for a connection to my HttpListener:
C# code:
var conn = await httpListener.GetContextAsync();
But oh no! Somewhere else, someone is trying to shut the listener down!
C# code:
httpListener.Stop();
And the await throws an exception:
code:
A first chance exception of type 'System.Net.HttpListenerException' occurred in mscorlib.dll

Additional information: The I/O operation has been aborted because of either a thread exit or an application request
What stupid thing am I doing?

kingcrimbud
Mar 1, 2007
Oh, Great. Now what?

GrumpyDoctor posted:

Here I am patiently waiting for a connection to my HttpListener:
C# code:
var conn = await httpListener.GetContextAsync();
But oh no! Somewhere else, someone is trying to shut the listener down!
C# code:
httpListener.Stop();
And the await throws an exception:
code:
A first chance exception of type 'System.Net.HttpListenerException' occurred in mscorlib.dll

Additional information: The I/O operation has been aborted because of either a thread exit or an application request
What stupid thing am I doing?

You're not running this in a console project are you? I've forgotten to Console.Readkey or whatever before when awaiting in a quick POC project and seen similar results.

ljw1004
Jan 18, 2005

rum

GrumpyDoctor posted:

Here I am patiently waiting for a connection to my HttpListener: var conn = await httpListener.GetContextAsync();
But oh no! Somewhere else, someone is trying to shut the listener down! httpListener.Stop();
And the await throws an exception: A first chance exception of type 'System.Net.HttpListenerException' occurred in mscorlib.dll

What stupid thing am I doing?

I don't understand. What you wrote seems fine. What actually is the problem? Is it that you don't want someone to shut the listener down? (if so you'll have to figure out why the Stop routine is being called and no one here can help you...)

raminasi
Jan 25, 2005

a last drink with no ice

kingcrimbud posted:

You're not running this in a console project are you? I've forgotten to Console.Readkey or whatever before when awaiting in a quick POC project and seen similar results.

Nope.

ljw1004 posted:

I don't understand. What you wrote seems fine. What actually is the problem? Is it that you don't want someone to shut the listener down? (if so you'll have to figure out why the Stop routine is being called and no one here can help you...)

Is there really no way to shut a listener down without having it throw an exception?

EssOEss
Oct 23, 2006
128-bit approved
Shutting it down without throwing an exception would be very bad design - you are using an object that has been shut down in the middle of an operation! This is most definitely an exceptional situation. Everything is as it should be.

I do understand that the code using HttpListener might find such situations uncomfrotable to handle. If this causes issues in your business logic, I recommend you ensure that all asynchronous work is complete before shutting it down. This may require you to implement custom synchronization logic. If anything is wrong in this picture, it is the fact that you are shutting down something you are still using.

Mr Shiny Pants
Nov 12, 2012

GrumpyDoctor posted:

Nope.


Is there really no way to shut a listener down without having it throw an exception?

Nope, not that one. I've run into the same issue.

The guys from Nancy seem to have fixed it, might want to check their code.

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy

GrumpyDoctor posted:

Is there really no way to shut a listener down without having it throw an exception?

You could call Stop(), wait for the current requests to complete, then call Close(). Inevitably, though, you will want to Close() after a certain amount of time anyway (if a request takes too long to complete) and an exception will be thrown regardless.

raminasi
Jan 25, 2005

a last drink with no ice

EssOEss posted:

Shutting it down without throwing an exception would be very bad design - you are using an object that has been shut down in the middle of an operation! This is most definitely an exceptional situation. Everything is as it should be.

I do understand that the code using HttpListener might find such situations uncomfrotable to handle. If this causes issues in your business logic, I recommend you ensure that all asynchronous work is complete before shutting it down. This may require you to implement custom synchronization logic. If anything is wrong in this picture, it is the fact that you are shutting down something you are still using.

There's no work to complete. The listener is idle.

Bognar posted:

You could call Stop(), wait for the current requests to complete, then call Close(). Inevitably, though, you will want to Close() after a certain amount of time anyway (if a request takes too long to complete) and an exception will be thrown regardless.

There are no current requests. I can understand that tearing down a listener that's actually connected to something is exceptional, but this one is just sitting there waiting.

The reason I'm confused about this is that it seems like I can do what I want in F# (this code is not useful for anything other than illustrating what I don't get):

code:
type Server() =
    static let listener = new HttpListener()
    static let mutable webSocket = None
    static do
        listener.Prefixes.Add("http://localhost:31415/")

    static member internal IsListening = listener.IsListening

    static member internal WebSocket = webSocket

    static member internal Start() =
        if Server.IsListening then () else
        listener.Start()
        let rec acceptConnections = async {
            let! connection =
                Async.AwaitTask <| listener.GetContextAsync()
            let! wsContext =
                Async.AwaitTask <| connection.AcceptWebSocketAsync("whatever", TimeSpan(0, 0, 15))
            webSocket <- Some(wsContext.WebSocket)
            return! acceptConnections }
        Async.Start acceptConnections

    static member internal Stop() =
        if not Server.IsListening then () else
        listener.Stop()
No exceptions are thrown if no connections are established. (At least, my attached debugger isn't seeing any.) Again, I expected to be missing something major somewhere, but I did not expect that "Stopping an idle listener is considered exceptional" was it. Under that logic, every call to HttpListener.Stop() will throw an exception somewhere, right?

raminasi fucked around with this message at 17:49 on Nov 4, 2014

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy
I am not terribly knowledgeable in F#, but isn't Async.Start similar to Task.Run in that the work is being queued on the threadpool? If an exception is thrown in that case, you wouldn't be able to catch it anywhere.

After a bit more research it seems that, yes, any call to GetContext() that is blocking (or awaiting GetContextAsync()) will unavoidably throw an exception on Stop() or Close().

EssOEss
Oct 23, 2006
128-bit approved
Waiting for an async call to complete *is* work that it is doing. Ideally, I would expect there to at least be some CancellationToken given to the HttpListener so you can instruct it to cancel the ongoing operation before stopping it. However, it seems that the API is not that nice - it really seems to be designed to be used strictly from one thread (even if it is thread-safe).

Withnail
Feb 11, 2004
Anyone know the best configuration approach to have both windows authentication for an MVC5 intranet project but also allow anonymous requests for web api calls in the same app?

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy

Withnail posted:

Anyone know the best configuration approach to have both windows authentication for an MVC5 intranet project but also allow anonymous requests for web api calls in the same app?

Currently, MVC and Web API have different setup methods (look at Global.asax.cs), so you should be able to apply a global MVC filter in FilterConfig, but not require that in Web API (which is in WebApiConfig). Alternately, you could just add the [AllowAnonymous] attribute to all of your Web API controllers.

Although, my XY spidey sense is tingling. Why don't you want the API calls authenticated?

ljw1004
Jan 18, 2005

rum

GrumpyDoctor posted:

The reason I'm confused about this is that it seems like I can do what I want in F# (this code is not useful for anything other than illustrating what I don't get):
code:
    static member internal Start() =
        if Server.IsListening then () else
        listener.Start()
        let rec acceptConnections = async {
            let! connection =
                Async.AwaitTask <| listener.GetContextAsync()
            let! wsContext =
                Async.AwaitTask <| connection.AcceptWebSocketAsync("whatever", TimeSpan(0, 0, 15))
            webSocket <- Some(wsContext.WebSocket)
            return! acceptConnections }
        Async.Start acceptConnections
No exceptions are thrown if no connections are established. (At least, my attached debugger isn't seeing any.)

Is your debugger set to catch first-chance exceptions or merely unhandled exceptions? I've not used Async.Start, but from my reading of the docs it merely kicks off an async workflow without actually awaiting for the result. So it's possible that the F# code is getting the same exceptions but they're just being ignored...


The design you have (with a method called "Stop()") isn't great. Cancellation tokens are always better. That's me passing on the considered conclusion of a lot of async experts :)

Now it's true that there are a bunch of APIs which don't take cancellation tokens. That's a shame, but one that we can do something about. I wrote here
http://blogs.msdn.com/b/lucian/archive/2012/12/08/await-httpclient-getstringasync-and-cancellation.aspx
about another case very similar to yours - the HttpResponse.Content.ReadAsStringAsync() method doesn't accept a cancellation token, so you have to explicitly call a Stop method (actually in this case a Dispose method) and it throws an exception.


quote:

Again, I expected to be missing something major somewhere, but I did not expect that "Stopping an idle listener is considered exceptional" was it. Under that logic, every call to HttpListener.Stop() will throw an exception somewhere, right?

Note that in .NET the pattern is that cancellation throws an exception (OperationCancelledException). So please don't feel too squeamish about having cancellation exposed as an exception!

Some people will reasonably argue that "await httpListener.GetContextAsync()" should return NULL if the httpListener has already been stopped. Others will reasonably argue that it should throw an exception. Both behaviors will be fairly surprising the first time you see them but at least the exception is more informative!

Mr Shiny Pants
Nov 12, 2012
Quick question about cancellation tokens I hope.

How do they work? I understand that you pass them to some Async job and you cancel them with CancellationToken.Cancel().

If I have multiple jobs, do I create multiple and pass them in one per job? If yes, do I need to do the bookkeeping myself of which token belongs to which job?

I haven't found a satisfactory answer to this, all examples are with one job and one token.

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy
CancellationToken is basically a wrapper around a boolean. You can use the CancellationTokenSource to create a CancellationToken and cancel that token. APIs that accept a CancellationToken are expected to check the token to see whether the operation is canceled before starting or continuing the operation.

For example, check out the code for Stream's ReadAsync method that takes a CancellationToken:

http://referencesource.microsoft.com/#mscorlib/system/io/stream.cs,417

It checks cancellationToken.IsCancellationRequested and exits if it's true (by calling Task.FromCancellation).

Do you want to cancel multiple tasks as part of one step? Use the same token. Do you want to cancel multiple tasks separately? Use different tokens.

EDIT: Note that there is some additional magic that implementations can do to register callbacks when the token is canceled, but for now pay no attention to the man behind the curtain.

Bognar fucked around with this message at 22:32 on Nov 6, 2014

Mr Shiny Pants
Nov 12, 2012
Ok, let's say I have a server that has multiple tasks in flight.

I create a new cancellationsource for the tasks I spin up. These might be different tasks.

Do I create a cancellationsource per Task? Or does cancellationsource.token create a new one each time it is called?

Do I put all these tokens in an array so I know what tasks are in flight and in the event of a shutdown I need to signal to cancel?

Bognar
Aug 4, 2011

I am the queen of France
Hot Rope Guy
Treat each CancellationTokenSource as one "logical" cancellation action. Depending on how you want to cancel all of your tasks, you might have one or many CancellationTokenSources. If you want to cancel them all at once, you can use one source. If you want to cancel different tasks at different times, use different sources. It really depends on how you want to do it.

Calling CancellationTokenSource.Token returns a new token each time, however each CancellationToken has a reference to its CancellationTokenSource (where you call Cancel) so you can treat them as effectively the same. Checking if a CancellationToken is canceled simply defers to checking if its source is canceled.

Regarding knowing what tasks are executing, you should know that the CancellationToken can outlast the tasks that run on it and are no indication of whether a task is running - only if it was told to be canceled or not. If you want to keep track of tasks, just hold onto the Task objects themselves.

Mr Shiny Pants
Nov 12, 2012
Thanks, much appreciated.

The MUMPSorceress
Jan 6, 2012


^SHTPSTS

Gary’s Answer
That word document analyzer I was talking about before is done. I was having this weird issue where comments would be out of order if I tried to look into tables. I tried a bunch of different approaches, but they'd always wind up out of order. In exasperation I looked in the .xml file for comments and noticed every comment had an ID of 0. No wonder.

Thing is, I didn't write the code that was handling finding the next comment id to use. I copied it from here: http://msdn.microsoft.com/en-us/library/office/cc850832%28v=office.15%29.aspx

The offending bit appears to be this:
code:
if (document.MainDocumentPart.GetPartsCountOfType<WordprocessingCommentsPart>() > 0)
{
    comments = 
        document.MainDocumentPart.WordprocessingCommentsPart.Comments;
    if (comments.HasChildren)
    {
        id = comments.Descendants<Comment>().Select(e => e.Id.Value).Max(); //THIS GUY RIGHT HERE, it's using a 
    }
}
else
{
    WordprocessingCommentsPart commentPart = 
                document.MainDocumentPart.AddNewPart<WordprocessingCommentsPart>();
   commentPart.Comments = new Comments();
   comments = commentPart.Comments;
}
MS's code example uses this lamba expression to find the next ID to use. Thing is, while I understand them rudimentarily, I don't get it enough to work out why it's broken. At first I thought it was because the IDs are strings, but changing the expression to e => Convert.toInt32(e.Id) didn't work. Can someone who is more familiar with this stuff tell me how you would change it so that it doesn't return 0 every time?

ljw1004
Jan 18, 2005

rum

Mr Shiny Pants posted:

Thanks, much appreciated.

You should also be aware of CancellationTokenSource.CreateLinkedTokenSource


Scenario: you have one main CancellationTokenSource that is the one that will be fired when the user hits the Cancel button.

Then you want to start a subsidiary task with its own timeout. So you do
code:
void FooAsync(CancellationToken mainUserCancellation)
{
  var stopConcurrentTasks = new CancellationTokenSource();
  var linkedToken = CancellationTokenSource.CreateLinkedTokenSource(mainUserCancellation, stopConcurrentTasks.Token);
  var attempt1 = httpClient.GetStringAsync(uri, linkedToken);
  var attempt2 = httpClient.GetStringAsync(uri, linkedToken);
  var attempt3 = httpClient.GetStringAsync(uri, linkedToken);
  var winner = await Task.WhenAll(attempt1, attempt2, attempt3);
  stopConcurrentTasks.Cancel();
}
That's because I want to cancel either if the user clicks the Cancel button, or if one of the other attempts at getting the string has completed quicker.

I might also have done
code:
stopConcurrentTasks.TimeoutAfter(1000)
to abandon all of them after 1 second.


Note: the difference between CancellationTokenSource and CancellationToken? the first is how you can send a request to cancel; the latter only has the power to listen for such requests.

Mr Shiny Pants
Nov 12, 2012
If you look at the comments collection in the debugger, do they have the right IDs? The code, from my understanding, should work.

Mr Shiny Pants
Nov 12, 2012

ljw1004 posted:

You should also be aware of CancellationTokenSource.CreateLinkedTokenSource


Thanks for the detailed explanation. One remark:
Should this:

code:
void FooAsync(CancellationToken mainUserCancellation)
{
  var winner = await Task.WhenAll(attempt1, attempt2, attempt3);
}
Be:
code:
void FooAsync(CancellationToken mainUserCancellation)
  var winner = await Task.WhenAny(attempt1, attempt2, attempt3);
}
?

epswing
Nov 4, 2003

Soiled Meat
The "help file" that ships with our software is a directory containing html files, which comes from an MS Word document -> Save As Web Page :stare:

Part of the reason why our documentation isn't great is because one guy here "owns" that word document. It would be way better if it was public (within the company), and a group of people could edit it, and it would have edit versioning, and so on. Sounds like a wiki to me.

IIS is a dependency of our software, so I'm looking into .NET wikis and CMSs. Anyone have any suggestions? Something like http://userguide.tomecms.org/ looks like it might fit the bill, but I have no idea what's "good".

How do you provide Documentation / User Guides to your clients via the .NET stack?

The MUMPSorceress
Jan 6, 2012


^SHTPSTS

Gary’s Answer

Mr Shiny Pants posted:

If you look at the comments collection in the debugger, do they have the right IDs? The code, from my understanding, should work.

Nope. If I look in the debugger for a document that started with no comments, they all have an ID of 0. If I go with a document that already had a comment, they all have whatever ID the comment had.

Here's the entire method that actually appends comments:
A couple notes: I use a separate method to actually check if a comments part already exists and create it if necessary. That method runs once at the beginning of the file analysis. This method is called at the end of analyzing each paragraph to append the comment to the paragraph.

code:
public static void appendComment(Paragraph p, String c, WordprocessingDocument document)
        {
            Comments comments = document.MainDocumentPart.WordprocessingCommentsPart.Comments;
            String id = "0";
	    if (comments.HasChildren)
    		{
        		id = comments.Descendants<Comment>().Select(e => e.Id.Value).Max();
    		}
            Paragraph compar = new Paragraph(new Run(new Text(c)));
            Comment cmt = new Comment()
            {
                Id = id,
                Author = "Analyzer",
                Initials = "A",
                Date = DateTime.Now
            };
            cmt.AppendChild(compar);
            comments.AppendChild(cmt);
            comments.Save();
            p.InsertBefore(new CommentRangeStart() { Id = id }, p.GetFirstChild<Run>());
            var cmtEnd = p.InsertAfter(new CommentRangeEnd() { Id = id }, p.LastChild);
            p.InsertAfter(new Run(new CommentReference() { Id = id }), cmtEnd);
        }

Forgall
Oct 16, 2012

by Azathoth

LeftistMuslimObama posted:

Nope. If I look in the debugger for a document that started with no comments, they all have an ID of 0. If I go with a document that already had a comment, they all have whatever ID the comment had.

Here's the entire method that actually appends comments:
A couple notes: I use a separate method to actually check if a comments part already exists and create it if necessary. That method runs once at the beginning of the file analysis. This method is called at the end of analyzing each paragraph to append the comment to the paragraph.

code:
public static void appendComment(Paragraph p, String c, WordprocessingDocument document)
        {
            Comments comments = document.MainDocumentPart.WordprocessingCommentsPart.Comments;
            String id = "0";
	    if (comments.HasChildren)
    		{
        		id = comments.Descendants<Comment>().Select(e => e.Id.Value).Max();
    		}
            Paragraph compar = new Paragraph(new Run(new Text(c)));
            Comment cmt = new Comment()
            {
                Id = id,
                Author = "Analyzer",
                Initials = "A",
                Date = DateTime.Now
            };
            cmt.AppendChild(compar);
            comments.AppendChild(cmt);
            comments.Save();
            p.InsertBefore(new CommentRangeStart() { Id = id }, p.GetFirstChild<Run>());
            var cmtEnd = p.InsertAfter(new CommentRangeEnd() { Id = id }, p.LastChild);
            p.InsertAfter(new Run(new CommentReference() { Id = id }), cmtEnd);
        }

So you take largest of existing IDs, which is zero, and you add a new comment with same ID which is still zero? I don't see any code to increment ID.

Adbot
ADBOT LOVES YOU

The MUMPSorceress
Jan 6, 2012


^SHTPSTS

Gary’s Answer

Forgall posted:

So you take largest of existing IDs, which is zero, and you add a new comment with same ID which is still zero? I don't see any code to increment ID.

:facepalm:
Added this to the method and fixed it: id = Convert.ToString(Convert.ToInt32(id) + 1);

As usual, I assumed the complicated thing was the problem and didn't see the obvious thing right in front of my face.
Although that still makes MS's example bad because their code basically just grabs the first paragraph and sticks a new comment on it. They don't increment the ID either so their example would screw up a document that was already commented.

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