locked
Save records based user name RRS feed

  • Question

  • Hello All, 

    I have the following code to ensure a user is allowed to insert a new record on a table based on its user name, at the moment if the user name is correct it will save no problem, but if it is incorrect it will throw an execption

    partial void Absences_Inserting(Absence entity)

            {

                string userName = this.Application.User.Name.ToLower();

     

                if (this.DataWorkspace.MFAT.Employees.Where(e => e.EmployeeID == entity.Employee.EmployeeID && e.UserName.ToLower() == userName).Execute().Count() == 0)

                {

                    throw new DataServiceOperationException("Permission error: Cannot modify a record you dont own");

                }

            }

     

    I was wondering what I need to do to just give a PropertyError instead of throwing and exception. I tried to add "EntityValidationResultsBuilder" as a parameter to the method but it wouldn't work. Any ideas or methods to achieve this will be welcome. Thank you.

     

    Yanick N.


    Regards Yanick N.
    Sunday, June 19, 2011 11:36 AM

Answers

  • While it is critical to secure the middle tier, you may also want to consider adding client side logic that would prevent the user from deleting the entity they do not own in the first place.  This may provide a better user experience.  To do this I would recommend overridding the delete command on the screen so that you can implement custom _CanExecute logic.

    The other thing I wanted to mention is that the EntitySet Validate method does not get called when deleting entities.  The reason for this is that all of the changes are reverted to an entity when it is deleted therefore there are no state changes to be validated.  If you want to get the same client side validation experience for deleted entities, you can throw a ValidationException from you _Deleting method.

    The following snippet from a test application I have illustrates how this can be done.

        partial void TestEntitySet_Validate(TestEntity entity, EntitySetValidationResultsBuilder results)
        {
          if (entity.CreatedBy != this.Application.User.Name && !this.Application.User.HasPermission(Permissions.SecurityAdministration))
          {
            if (entity.Details.EntityState == EntityState.Modified)
            {
              results.AddEntityError("It is not valid to modify an Entity that you did not create");
            }
            else if (entity.Details.EntityState == EntityState.Deleted)
            {
              results.AddEntityError("It is not valid to delete an TestEntity that you did not create");
            }
          }
        }
    
        partial void TestEntitySet_Deleting(TestEntity entity)
        {
          if (entity.Details.ValidationResults.Any())
          {
            throw new ValidationException(null, null, entity.Details.ValidationResults);
          }
        }
    

    Monday, June 20, 2011 5:55 PM

All replies

  • Add your code to Absence_Validate instead of Absences_Inserting & you'll have access to an EntityValidationResultsBuilder as a parameter (called results) to the method, & you'll be able to call results.AddEntityError or results.AddPropertyError, depending on what suits you best. 



    Yann

    (plus ça change, plus c'est la même chose!)

    Sunday, June 19, 2011 12:45 PM
  • Hello Yann,

     

    Thank you for your help, I did thought about adding the check in that method, but what I actually want to do is allow users to view absence records for every one, but only be able to modify, add, or delete their own. This is why i thought this check should be done in the _inserting, _delete, and _updating methods. Is my approach wrong?

     

     


    Regards Yanick N.
    Sunday, June 19, 2011 2:36 PM
  • Yanick, in the inserting, deleting and updating methods you can discard the changes and show a message box. To discard the changes use something like "this.DataWorkspace.<Your Application Data>.Details.DiscardChanges()"

    Sunday, June 19, 2011 3:50 PM
  • Hi Yanick

    To resolve your case, I think you can serve this code taken from:

    http://msdn.microsoft.com/en-us/library/ff852065.aspx

    Add your custom validation code to the <entity or table name>[_Validate] method.

    To enforce a validation rule after a user adds, deletes, or updates a row of data, call the AddScreenError method of the results parameter.

    The following example prevents the deletion of customers who are located in the United States.

    Private Sub Customers_Validate _
      (results As Microsoft.LightSwitch.Framework.Client.ScreenValidationResultsBuilder)
      If Me.DataWorkspace.NorthwindData.Details.HasChanges Then
        Dim changeSet As EntityChangeSet = _
          Me.DataWorkspace.NorthwindData.Details.GetChanges()
    
        Dim entity As IEntityObject
    
        For Each entity In changeSet.DeletedEntities.OfType(Of Customer)()
    
          Dim cust As Customer = CType(entity, Customer)
          If cust.Country = "USA" Then
            entity.Details.DiscardChanges()
            results.AddScreenResult("Unable to remove this customer." & _
              "Cannot delete customers that are located in the USA.", _
               ValidationSeverity.Informational)
          End If
    
        Next
      End If
    
    End Sub
    


    Jaime

    I hope this helps!

     

     

    Sunday, June 19, 2011 4:12 PM
  • Thank you Ravi,

     

    Is there a direct way to show a message in LS? I tried to the "regular" way but it doesn't work. I aslo tried to add the "presentationframework" but it woudlnt work. any ideas?

     


    Regards Yanick N.
    Sunday, June 19, 2011 4:57 PM
  • Thank you for your input Jaime, but that snippet does not help me solved my problem. I used the "DiscarcChanges" methods, which stops the record from being saved, but what I am trying to achieve now is to notify the user via a message box, but there seems to be no easy or direct way to do so via a regular messageBox as in a regular application. The next thing I would like to be able to do is to call a sort of "refresh" method that will reload the data, this because Altough the DiscardChanges() works, the record still appears on the screen, and disappear only when the "refresh" button is pressed. But then again, even there there dont seem to be a direct way to access that method. 

     

    Thanks


    Regards Yanick N.
    Sunday, June 19, 2011 7:56 PM
  • Hi Serval

     

    Message box here is ShowMessageBox ( <Message>).

     

    Jaime

    Sunday, June 19, 2011 10:29 PM
  • Another thing

    There is not way to do "Refresh" by code. Sorry.

     

    Jaime

     

    Sunday, June 19, 2011 10:32 PM
  • Altough the DiscardChanges() works, the record still appears on the screen, and disappear only when the "refresh" button is pressed.

     

    Thanks


    Regards Yanick N.

    ?

    This example:

    http://lightswitchhelpwebsite.com/Forum/tabid/63/aft/72/Default.aspx

    You can play with it yorself with this live online example:

    http://lightswitchhelpwebsite.com/Demos/ThingsForSale.aspx

    (using the LightSwitch application (log in and scroll to the bottom of the page) you can click Respond to a message (you will need to create a post, then enter a message for that post using the HTML form), if you click the X in the upper right-hand corner of the "Respond Popup", it will close the popup and remove the record without requiring you to click Refresh.

    You can download the complete application at:

    http://www.codeproject.com/KB/silverlight/DNNThingsForSale.aspx


    http://www.adefwebserver.com

    http://LightSwitchHelpWebsite.com


    Sunday, June 19, 2011 11:11 PM
  • Hello Jaime,

     

    Have you tried calling it that way before? It sures doesnt work for me, so i would be really nice if you could let me know how.

     

     


    Regards Yanick N.
    Monday, June 20, 2011 1:03 AM
  • Salut Yanick,

    ShowMessageBox("Message Text Goes Here") is part of the Microsoft.LightSwitch.Presentation.Extensions assembly, & is an extension method of IScreenObject, so is only going to work in the Client project (in Screens).

    Are you perhaps trying to use it in code that's in the Server project?

     


    Yann

    (plus ça change, plus c'est la même chose!)

    Monday, June 20, 2011 1:24 AM
  • I think the best way to handle that is with Validate on the entity.Employee field on the table.  Because this respects the notion of multiple record validation.  Some may pass validation, others may fail.  The user has visual indication of which record fail and outline the Employee field (or other). A messagebox would not really fit the use case here unless you could restrict user to single record operations which is not really LS style.  Another pro is, you get indication on screen of issue(s) before the Save.

    partial void Type_Validate(EntityValidationResultsBuilder results)
    {
      if (this.Type == "EX")
        results.AddPropertyError("Invalid");
    }

    Monday, June 20, 2011 1:34 AM
  • While it is critical to secure the middle tier, you may also want to consider adding client side logic that would prevent the user from deleting the entity they do not own in the first place.  This may provide a better user experience.  To do this I would recommend overridding the delete command on the screen so that you can implement custom _CanExecute logic.

    The other thing I wanted to mention is that the EntitySet Validate method does not get called when deleting entities.  The reason for this is that all of the changes are reverted to an entity when it is deleted therefore there are no state changes to be validated.  If you want to get the same client side validation experience for deleted entities, you can throw a ValidationException from you _Deleting method.

    The following snippet from a test application I have illustrates how this can be done.

        partial void TestEntitySet_Validate(TestEntity entity, EntitySetValidationResultsBuilder results)
        {
          if (entity.CreatedBy != this.Application.User.Name && !this.Application.User.HasPermission(Permissions.SecurityAdministration))
          {
            if (entity.Details.EntityState == EntityState.Modified)
            {
              results.AddEntityError("It is not valid to modify an Entity that you did not create");
            }
            else if (entity.Details.EntityState == EntityState.Deleted)
            {
              results.AddEntityError("It is not valid to delete an TestEntity that you did not create");
            }
          }
        }
    
        partial void TestEntitySet_Deleting(TestEntity entity)
        {
          if (entity.Details.ValidationResults.Any())
          {
            throw new ValidationException(null, null, entity.Details.ValidationResults);
          }
        }
    

    Monday, June 20, 2011 5:55 PM
  • Hello all,

     

    I am not posting yet because I am trying all of the suggestion above, thank you all for your help, I will surely post back to report on how I got on. Thanks again.

     

    Yanick N.

    (plus ça change, plus c'est la même chose!) hmmm... J'en doute fort :) 

     


    Regards Yanick N.
    Tuesday, June 21, 2011 11:02 AM
  • @snomis.  +1 :)
    Tuesday, June 21, 2011 1:41 PM
  • Ok, Thanks to every body for their inputs, I did carry out a few test, and I also tried to implement the suggestions above. Please note that what I actually have is records on a table (Absence in this case) and I have some an Employee table. While it is possible for me to allow users to only view their records, via a query this is not actually what I would like to achieve. I would like all employees to view records from any others, but only have the possibility to insert/update/modify their own.

     

    For that reason I chose to implement the _deleting, _inserting, _updating method separately. Using "discardChanges()" as above works for me (Thanks Ravi), but the user experience is not so pleasant, i need to find a way to warn the user that the record will not be saved. 

    I cant use the ShowMessageBox directly because the method are implemented in the <appname>services.cs file (JaimeH: As you pointed out it will not work in this case.).

    I cannot auto refresh directly by making a function call.... (Limitation? im no expert)

     I cannot find a way to print a message on the screen, I cannot drill down to "Microsoft.LightSwitch.Framework.Client.ScreenValidationResultsBuilder" to access screen validation builder and put a message on the screen (JaimeH) I may

    well be missing an assembly but documentation and libraries of LS do not seem to come as well as other .net libraries in a search engine.

    William Stacey: Thanks for your input, but that validate is too general for me.

     

    After the Details.DiscardChanges() I tried to throw an execption, which works fine and stop the app, but I am not sure how the system will behave in a live environment,

    will it throw the exception and just send a message, or will it crash completely :(

     

    So to summarize, my validation are done in the appNameservices.cs file, and I need to find a way to:

    1) Inform a User via a messageBox, or just a message printed on the screen such as the screen property errors,

    2) refresh the screen.....

    I do find it hard to believe that there isn't a function call one could make such has screenname.refresh() it seems like quiete a handy function to me.

    Futhermore, the option of a message box should always be allowed, it is the first way to communicate directly with the user... That's my (not expert's) opinion.

     

     


    Regards Yanick N.
    Wednesday, June 22, 2011 7:08 PM
  • Hi Yanick

    It occurs to me propose this alternative:

    Create two screen for data format that you require, one for all to see information from all (by selecting "Use Read-Only Controls" in that screen) and a screen controlled by a button to call that before further invoke the screen verify that the user session is for the person selected in the list, so each person can edit only the information itself, leaving the field unchangeable person where you edit the information.

     

    Jaime

    Wednesday, June 22, 2011 9:13 PM
  • JaimeH

     

    Nice workaround, one screen for viewing but no edit options, and a separate one form editing which will load only current user data. I am not sure my boss will like that option, but I could always give it a go. 

    Thank you so much for your help, I didnt think about that.

     


    Regards Yanick N.
    Wednesday, June 22, 2011 10:07 PM
  • Yeh.  I might do a ListDetails screen for the read-only.  Then just override the Edit button on the list grid part that shows New Data/Edit screen or puts up a messagebox to say can't edit that record.  I would still do @snomis logic for the back-end protection and not rely on client logic.  They really need some lower CanUpdate(Entity), CanDelete(Entity entity) methods also. 

    Thursday, June 23, 2011 2:27 AM
  • Another thing you could do if, for example, on a ListDetail screen is make all controls readonly if not owner on selected item changed event.  Preventing changes before hand (i.e. readonly) probably makes more sense to the user with a visual clue. Instead of allowing changes that will fail anyway on Save.

    partial void Employees_SelectionChanged()
    {
      // If selected item is not owner, make all controls readonly.
      var emp = this.Employees.SelectedItem;
      if (emp != null)
      {
        var root = this.FindControl("RootContentItem"); // root screen control.
        root.IsReadOnly = !emp.IsOwner; // Calculated field checks current user owns record.
      }
    }

    Thursday, June 23, 2011 6:58 AM
  • I would use the method that Snomis suggested "To do this I would recommend overridding the delete command on the screen so that you can implement custom _CanExecute logic". By doing so, you don't need any error message, because you're preventing the user from doing the "wrong" thing in the first place.

    VB:

     

            Private Sub DeleteEntityButton_CanExecute(ByRef result As Boolean)
    		result = (Me.EntityCollectionName.SelectedItem IsNot Nothing
    			AndAlso (Me.EntityCollectionName.SelectedItem.CreatedBy = Me.Application.User.Name)
            End Sub
    

     

    C#:

     

    		private void DeleteEntityButton_CanExecute(ref bool result)
    		{
    		result = (this.EntityCollectionName.SelectedItem != null);
    		&& (this.EntityCollectionName.SelectedItem.CreatedBy == this.Application.User.Name);
    		}
    

     


    Yann

    (plus ça change, plus c'est la même chose!)


    • Edited by Yann Duran Thursday, June 23, 2011 10:32 PM Fixed formatting
    Thursday, June 23, 2011 8:19 AM
  • But you don't want them to update or try to update either.  So Snomis suggestion for biz logic rule and add the Readonly stuff I showed for client side. If you only do snomis, then user gets validation errors on save if they change anything.  Then forced to Refresh and discard all changes to clear them which can leave user confused, especially if they made valid changes to the record(s) they are allowed to edit. 
    Thursday, June 23, 2011 7:43 PM
  • Hello William,

    I tried to implement this method also, but I cant find the <screen/tablename>_SelectionChanged() method, but it does not seems to be listed in the "write code" section, and even when I try to implement it directly it does give an error. 

    The screen is composed of 3 tables, and even when I select the particular field, it still doesn't appear.

    Not sure where to go from here. :( thank you for your help...


    Regards Yanick N.
    Thursday, June 23, 2011 11:30 PM
  • On the left side of designer.  Select the query that your talking about (click on it).  The "Write Code" dropdown will then have a item for SelectionChanged.
    Thursday, June 23, 2011 11:57 PM