0

I am new to Web services (WebApi) development, but it's been wonderful experience till now :). But right now, I am confused with the way I am handling Put() requests in my web service.

I have a Put() request which receives multipart/form-data. I have to check whether the Form body values are null or not before updating the corresponding Entity.

If a particular property of Entity is null i.e. not present in the body, I should not make any changes to property in which case property will have previously set value. If there's a value present in the body for a property, then I need to update the corresponding value for the entity.

I used if-else, but it caused lot of code as I have around 10 properties for the entity. So I removed all the if-else and used reflection to achieve this. Here's the code which I am using currently,

    [AuthorizationRequired]
    [Route("v1/product/updateproduct/")]
    [HttpPut]
    public bool Put()
    {
        var httpRequest = HttpContext.Current.Request;
        string pdfFolderPath = HttpContext.Current.Server.MapPath("~/Pdf/");
        string imageFolderPath = HttpContext.Current.Server.MapPath("~/Images/");
        ProductEntity productToUpdate = new ProductEntity();

        int id = int.Parse(httpRequest["ProductId"].ToString());

        if (id > 0)
        {
            //Get the existing product entity for the ID
            ProductEntity existingProduct = _productServices.GetProductById(id);

            if (existingProduct != null)
            {
                //Check if any files are sent in the PUT Request
                //If there are files with appropriate names, then delete the existing files with the new files sent
                if (httpRequest.Files.Count > 0)
                {
                    //Handle Files.
                }

                //Updating the changed properties of Product by using Reflection.
                PropertyInfo[] properties = productToUpdate.GetType().GetProperties();

                foreach (PropertyInfo property in properties)
                {
                    //Check if the property of the Product class exists in the Form Body of Put.
                    //If it exists, take the corresponding value of property and update it to the product.
                    //If it doesn't exist, retain the value present in the existing product as  it is.

                if (httpRequest.Form.AllKeys.Contains(property.Name, StringComparer.OrdinalIgnoreCase))
                {
                    int formPropertyIndex = Array.FindIndex(httpRequest.Form.AllKeys, str => str.Equals(property.Name, StringComparison.OrdinalIgnoreCase));
                    string propertyToFind = httpRequest.Form.AllKeys[formPropertyIndex];

                    if (String.Equals(propertyToFind, "FacilityId", StringComparison.OrdinalIgnoreCase))
                    {
                        productToUpdate.GetType().GetProperty(propertyToFind, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).SetValue(productToUpdate, Convert.ToInt32(httpRequest[property.Name]));
                    }
                    else
                    {
                        productToUpdate.GetType().GetProperty(propertyToFind, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).SetValue(productToUpdate, httpRequest[property.Name].ToString());
                    }
                }
                else
                    {
                        var existingValue = existingProduct.GetType().GetProperty(property.Name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).GetValue(existingProduct);
                        productToUpdate.GetType().GetProperty(property.Name, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance).SetValue(productToUpdate, existingValue);
                    }
                }

                if (productToUpdate.ImagePath == null)
                    productToUpdate.ImagePath = existingProduct.ImagePath;
                if (productToUpdate.AR_PdfPath == null)
                    productToUpdate.AR_PdfPath = existingProduct.AR_PdfPath;
                if (productToUpdate.AR_Alt_PdfPath == null)
                    productToUpdate.AR_Alt_PdfPath = existingProduct.AR_Alt_PdfPath;
                if (productToUpdate.EN_PdfPath == null)
                    productToUpdate.EN_PdfPath = existingProduct.EN_PdfPath;
                if (productToUpdate.EN_Alt_PdfPath == null)
                    productToUpdate.EN_Alt_PdfPath = existingProduct.EN_Alt_PdfPath;

                //Update LastModifiedBy & LastModifiedAt
                string token = httpRequest.Headers.GetValues("Token").ToList().FirstOrDefault();
                UserEntity user = _tokenServices.GetUserDetailsForToken(token);
                productToUpdate.LastModifiedBy = user.UserName;
                productToUpdate.LastModifiedAt = DateTime.Now;

                return _productServices.UpdateProduct(id, productToUpdate);
            }
            else
            {
                return false;
            }
        }
        return false;
    }

The above code does the job, but the question is - Is this the only way or is there any better way to achieve what I am trying to do? If there is, few pointers on those approach would be really appreciated.

Thanks In Advance :)

iamyogish
  • 2,372
  • 2
  • 23
  • 40
  • ?: or ?? operator might help avoiding if else statement in many scenario – Kira Mar 04 '16 at 10:50
  • Can you post the code of your "put" method ? – ADreNaLiNe-DJ Mar 04 '16 at 10:51
  • @Anand Yeah null coalescing operator would be good. But again if I have way too many properties, I would be repeating the operation for all the properties. – iamyogish Mar 04 '16 at 10:52
  • It's a better practice to test each property individually than using reflection. You should refer to this post http://stackoverflow.com/questions/224232/what-is-the-cost-of-net-reflection – ADreNaLiNe-DJ Mar 04 '16 at 10:55
  • @Yogish, if you have many properties, its better to set values for properties directly. Using reflection may degrade performance. Usually I use reflection for data binding because I only know the property name in data source. – Kira Mar 04 '16 at 11:05

1 Answers1

2

I ever heard about reflection approach i "slow", more slower than other but i've alwais use it when i don't know the class structure.

If you know class structure is a good idea use his properties.

If you can modify classes you should use validation with dataannotation (https://msdn.microsoft.com/en-us/library/dd901590(VS.95).aspx) to check object.

MCV do some work very well, one of them is "recreate" objects in methods from html form posted via HttpRequest. I'll try everytime to use this functions before access raw posted data.

In your case you know type, you know property names, so i think than you can reduce your code to a more simple and standard pattern

For example:

[HttpPut]
public HttpResponseMessage Put(int productId, [FromBody] Product product)
{
//..
}

Object Product is recreated from mvc engine for you, so you can use it in a more comfortable way

  • But my Put() request is of multipart/form-data, so can I use this prototype? public HttpResponseMessage Put(int productId, [FromBody] Product product) ; – iamyogish Mar 04 '16 at 11:01
  • However I have used the same pattern of taking help of MVC to recreate objects from the formdata using [FromBody]. But not sure how to use it for multipart/form-data. – iamyogish Mar 04 '16 at 11:03
  • 1
    MVC uses model binder to bind the value from client and parameters of action result. If you know your class structure and pass the data accordingly from client, MVC will bind the property with corresponding value – Kira Mar 04 '16 at 11:12
  • MVC use a little trick... the posted data will "fill" the class used as parameters in method using property names and form keys. If your input parameter object not contains all information you can choose to manage them in other way . There is another comsequence... you can use some "DTO objects" in wep api method that are'nt part of datamodel, but MVC fill it well as others... (think about an ProductDTO or ProductVM) – Othello .net dev Mar 04 '16 at 11:29