views:

69

answers:

2

Hi everyone. I'm having an issue with attempting to set a property on a remote object hosted in a Windows Service. I'm trying to change a property of an object and it is not saving for some reason.

Here is the pertinent code of the service:

    private static List<Alert> _alerts = new List<Alert>(); // List of the Alerts
    private TcpChannel _tcpChannel;

    protected override void OnStart(string[] args)
    {
        loadAlerts(); // This sets up the List (code not req'd)
        // Set up the remotelister so that other processes can access _alerts
        // Create the TcpChannel
        _tcpChannel = new TcpChannel(65000);
        ChannelServices.RegisterChannel(_tcpChannel, false);

        // Register the Proxy class for remoting.
        RemotingConfiguration.RegisterWellKnownServiceType(
          typeof(RemoteLister),
          "AlertList.soap",
          WellKnownObjectMode.Singleton);
    }

    [Serializable]
    public class RemoteLister : MarshalByRefObject
    {
        public List<Alert> TheList
        {
            get { return _alerts; }
            set { _alerts = value; }
        }

        public bool save()
        {
            EventLog.WriteEntry("AlertService", "Calling saveAlerts...");
            return saveAlerts();
        }
    }

Here is the code for the Alert class (a lot of other stuff too):

    private string _alertName; // Name of alert

    public string AlertName
    {
        get { return _alertName; }
        set { _alertName = value; }
    }

Now in my ASP.NET web app, here's how I initialize everything:

AlertService.RemoteLister remoteAlertList;

    protected void Page_Load(object sender, EventArgs e)
    {
        // This is where we create a local object that accesses the remote object in the service
        Type requiredType = typeof(AlertService.RemoteLister);
        // remoteAlertList is our reference to the List<Alert> in the always running service
        remoteAlertList = (AlertService.RemoteLister)Activator.GetObject(requiredType,
                "tcp://localhost:65000/AlertList.soap");
    }

So now the following code works:

private void fillFields()
    {
        AlertNameTextBox.Text = remoteAlertList.TheList[AlertDropDownList.SelectedIndex].AlertName;
    }

But when I go to change that property as in the following, it doesn't work.

 protected void AlertSaveButton_Click(object sender, EventArgs e)
    {
        remoteAlertList.TheList[AlertDropDownList.SelectedIndex].AlertName = AlertNameTextBox.Text;
    }

Does anyone have an idea of why it wouldn't save that property?

Thanks in advance!

+1  A: 

I would guess that since List<T> doesn't inherit from MarshalByRefObject, when you call the property remoteAlertList.TheList you get a disconnected object. Perhaps add an indexer to the object instead:

public class RemoteLister : MarshalByRefObject
{
    public Alert this[int index] {get {...} set {...}}
}

Actually, I would mainly say ditch remoting and use WCF/SOA. And there is little purpose in RemoteLister being [Serializable]. You might also want to put some thought into thread-safety.


To clarify; the Alert instances will also be standalone, so local updates won't affect the server; basically, you have two scenarios:

  • if the type is MarshalByRefObject, then it only lives at the server; all client operations are remoted to the actual object - but only for MarshalByRefObjecf types
  • otherwise, the object is serialized and an actual object is reconstructed.

If you an indexer with (for example)

obj[index].Name = "abc";

then this is:

var tmp = obj[index]; // tmp points to the deserialized disconnected Alert
tmp.Name = "abc"; // we update the local disconnected Alert

(if Alert was MarshalByRefObject, this would update the server, but don't do that). But if we push the value back:

obj[index] = tmp;

then we have updated the server.

As you've discovered, an operation-centric design may be far simpler (i.e. setAlertName). But I really think remoting is a bad idea here.

Marc Gravell
Tried doing it as an indexer... no luck.Regarding the serializable suggestion, Marc - You're right, it doesn't serve any purpose. I'm very new to C# and I was running into errors when accessing it at first that said it needed to be serializable, so I just added [Serializable] here and to the actual Alert class and it worked. I think I only needed it on the Alert class.And as for thread-safety, good idea. Although it likely won't be accessed by more than one thread at once, it is possible and i'll look into that.
sabad66
Why do you think remoting is a bad idea? It seems like the simplest way to implement this... I looked into WCF briefly and it just seems like a lot of extra work and overhead. The windows service and the web app are on the same server if that makes a difference. I am learning and I would like to do this the "best" way so I'd be really interested to hear your suggestion on how to do this instead.
sabad66
WCF isn't really much harder to setup (nor more overhead) than remoting. Simpler in many ways. If it works for you with remoting, then fine. And indeed not having to navigate firewalls makes things simpler. In *general* I believe WCF has a lot more flexibility, but if you don't need it... (I didn't refuse to answer a remoting question, after all). Perhaps just take it as "also consider WCF", which it sounds like you've already done...
Marc Gravell
A: 

Tried the indexer, no luck with that. I tried another way shown below and it worked, but it seems like a workaround and not the proper way of doing it. All I did was add this in the RemoteLister class:

public void setAlertName(int index, string name)
            {
                _alerts[index].AlertName = name;
            }

And then changed the save button as follows:

protected void AlertSaveButton_Click(object sender, EventArgs e)     
{     
    remoteAlertList.setAlertName(AlertDropDownList.SelectedIndex, AlertNameTextBox.Text);    
}

I suppose this will get me by for now, but i'd rather do it the "right" way if anyone knows why it wasn't working the first way I did it.

sabad66
Just an FYI...One free tool to use that helps you do things the "right" way is FxCop. It analyses your code and will recommend things that Visual Studio will not. For example, it would tell you to not expose your generic list through a property. Rather, use the indexer to set items, and expose a read only list for the get. This allows you to control what and when things get added to the list. If you expose it, anything consuming your class can alter its state at any time. Just some thoughts.
Aaron Daniels
Thanks Aaron, I'll check it out! I really appreciate the help from this board!
sabad66