我怎么能强制DbUpdateConcurrencyException即使我传递的FormCollection到我的帖子的行动方法,而不是一个对象的拟募集 [英] How can i force the DbUpdateConcurrencyException to be raised even if i am passing the FormCollection to my Post action method instead of an object

查看:193
本文介绍了我怎么能强制DbUpdateConcurrencyException即使我传递的FormCollection到我的帖子的行动方法,而不是一个对象的拟募集的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有以下操作方法我的asp.net mvc的web应用程序里面,这将提高一个 DbUpdateConcurrencyException 作为用于处理可能发生的任何冲突,并发: -

i have the following action method inside my asp.net mvc web application , which will raise a DbUpdateConcurrencyException as intended to handle any concurrent conflicts that might happen:-

[HttpPost] 
        public ActionResult Edit(Assessment a) 
        {            try 
            { 
                if (ModelState.IsValid)  
                { 
                    elearningrepository.UpdateAssessment(a); 
                    elearningrepository.Save(); 
                    return RedirectToAction("Details", new { id = a.AssessmentID }); 
                } 
            } 
            catch (DbUpdateConcurrencyException ex) 
            { 
                var entry = ex.Entries.Single(); 
                var clientValues = (Assessment)entry.Entity; 

            ModelState.AddModelError(string.Empty, "The record you attempted to edit was"  
                 + "modified by another user after you got the original value."); 
                               } 
            catch (DataException) 
            { 
                ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator."); 
            }        
            return View(a);}

但要避免过度绑定攻击我必须定义一个 [绑定(包括=日期,标题)] 的对象类,但是这提出了一个问题我作为上述操作方法,即使没有并发冲突发生监守模型绑定将无法绑定对象ID和其他值,,所以我改变了我的操作方法下面会返回一个例外: -

but to avoid any over binding attacks i have define a [Bind(Include = "Date, Title")] on the object class, but this raised a problem to me as the above action method will return an exception even if no concurrent conflict occur becuase the model binder will not be able to bind the object ID and other values ,, so i have changed my action method to the following:-

[HttpPost] 
        public ActionResult Edit(int id, FormCollection collection) 
        { 
            Assessment a = elearningrepository.GetAssessment(id); 

            try 
            { 
                if (TryUpdateModel(a)) 
                { 
                    elearningrepository.UpdateAssessment(a); 
                    elearningrepository.Save(); 
                    return RedirectToAction("Details", new { id = a.AssessmentID }); 
                } 
            } 
            catch (DbUpdateConcurrencyException ex) 
            { 
                var entry = ex.Entries.Single(); 
                var clientValues = (Assessment)entry.Entity; 

            ModelState.AddModelError(string.Empty, "The record you attempted to edit was"  
                 + "modified by another user after you got the original value."); 
            } 
            catch (DataException) 
            {                ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator."); 
            }return View(a); 

但书面方式操作方法,在第二种方法将不会提高 DbUpdateConcurrencyException 的形势下(即使出现并发冲突!)。

but writting the action method as in the second approach will not raise the DbUpdateConcurrencyException under any situation (even if a concurrency conflict occurs!!!).

所以我的问题是如何,我可以确保该 DbUpdateConcurrencyException 将被告知是否有冲突发生,并在同一时间,以确保不会过绑定募集攻击可能通过定义发生 [绑定(包括=日期,标题)]
在此先感谢您的帮助和建议。

BR

so me question is how i can make sure that the DbUpdateConcurrencyException will be raised if any conflict occur and at the same time to make sure that no over binding attack might occur by defining [Bind(Include = "Date, Title")]? thanks in advance for any help and suggestions . BR

推荐答案

停止使用形式收集和使用视图模型,那是一个更好的方法。

Stop using forms collection and use a view model, thats a far better approach.

另外我有一个动作过滤器,我写来处理并发异常(MVC4现在处理实体例外终于有了验证拍拍只是没有并发例外)。它的工作正在进行中,但应工作正常的是,很多已经过测试:)

Also I have an action filter I wrote to handle the concurrency exceptions (MVC4 handles entity exceptions now finally has pat of validation just not the concurrency exceptions). Its a work in progress but should work ok as is, that much has been tested : )



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Data.Entity.Infrastructure;
using System.Reflection;

namespace Gecko.Framework.Mvc.ActionFilters
{
    /// <summary>
    /// Author: Adam Tuliper
    /// adam.tuliper@gmail.com
    /// completedevelopment.blogspot.com
    /// www.secure-coding.com
    /// Use freely, just please retain original credit.
    /// 
    /// This attribute attempts to intercept DbUpdateConcurrencyException to write out original/new values
    /// to the screen for the user to review.
    /// It assumes the following:
    /// 1. There is a [Timestamp] attribute on an entity framework model property
    /// 2. The only differences that we care about from the posted data to the record currently in the database are 
    /// only yhe model state field. We do not have access to a model at this point, as an exception was raised so there was no
    /// return View(model) that we have a model to process from.
    /// As such, we have to look at the fields in the modelstate and try to find matching fields on the entity and then display the differences.
    /// This may not work in all cases. 
    /// This class will look at your model to get the property names. It will then check your
    /// Entities current values vs. db values for these property names.
    /// The behavior can be changed.
    /// </summary>
    public class HandleConcurrencyException : FilterAttribute, IExceptionFilter //ActionFilterAttribute
    {
        private PropertyMatchingMode _propertyMatchingMode;
        /// <summary>
        /// This defines when the concurrencyexception happens, 
        /// </summary>
        public enum PropertyMatchingMode
        {
            /// <summary>
            /// Uses only the field names in the model to check against the entity. This option is best when you are using 
            /// View Models with limited fields as opposed to an entity that has many fields. The ViewModel (or model) field names will
            /// be used to check current posted values vs. db values on the entity itself.
            /// </summary>
            UseViewModelNamesToCheckEntity = 0,
            /// <summary>
            /// Use any non-matching value fields on the entity (except timestamp fields) to add errors to the ModelState.
            /// </summary>
            UseEntityFieldsOnly = 1,
            /// <summary>
            /// Tells the filter to not attempt to add field differences to the model state.
            /// This means the end user will not see the specifics of which fields caused issues
            /// </summary>
            DontDisplayFieldClashes = 2
        }

        /// <summary>
        /// The main method, called by the mvc runtime when an exception has occured.
        /// This must be added as a global filter, or as an attribute on a class or action method.
        /// </summary>
        /// <param name="filterContext"></param>
        public void OnException(ExceptionContext filterContext)
        {
            if (!filterContext.ExceptionHandled && filterContext.Exception is DbUpdateConcurrencyException)
            {
                //Get original and current entity values
                DbUpdateConcurrencyException ex = (DbUpdateConcurrencyException)filterContext.Exception;
                var entry = ex.Entries.Single();
                //problems with ef4.1/4.2 here because of context/model in different projects.
                //var databaseValues = entry.CurrentValues.Clone().ToObject();
                //var clientValues = entry.Entity;
                //So - if using EF 4.1/4.2 you may use this workaround
                var clientValues = entry.CurrentValues.Clone().ToObject();
                entry.Reload();
                var databaseValues = entry.CurrentValues.ToObject();

                List<string> propertyNames;

                filterContext.Controller.ViewData.ModelState.AddModelError(string.Empty, "The record you attempted to edit "
                        + "was modified by another user after you got the original value. The "
                        + "edit operation was canceled and the current values in the database "
                        + "have been displayed. If you still want to edit this record, click "
                        + "the Save button again to cause your changes to be the current saved values.");
                PropertyInfo[] entityFromDbProperties = databaseValues.GetType().GetProperties(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Instance);

                if (_propertyMatchingMode == PropertyMatchingMode.UseViewModelNamesToCheckEntity)
                {
                    //We dont have access to the model here on an exception. Get the field names from modelstate:
                    propertyNames = filterContext.Controller.ViewData.ModelState.Keys.ToList();
                }
                else if (_propertyMatchingMode == PropertyMatchingMode.UseEntityFieldsOnly)
                {
                    propertyNames = databaseValues.GetType().GetProperties(BindingFlags.Public).Select(o => o.Name).ToList();
                }
                else
                {
                    filterContext.ExceptionHandled = true;
                    UpdateTimestampField(filterContext, entityFromDbProperties, databaseValues);
                    filterContext.Result = new ViewResult() { ViewData = filterContext.Controller.ViewData };
                    return;
                }



                UpdateTimestampField(filterContext, entityFromDbProperties, databaseValues);

                //Get all public properties of the entity that have names matching those in our modelstate.
                foreach (var propertyInfo in entityFromDbProperties)
                {

                    //If this value is not in the ModelState values, don't compare it as we don't want
                    //to attempt to emit model errors for fields that don't exist.

                    //Compare db value to the current value from the entity we posted.

                    if (propertyNames.Contains(propertyInfo.Name))
                    {
                        if (propertyInfo.GetValue(databaseValues, null) != propertyInfo.GetValue(clientValues, null))
                        {
                            var currentValue = propertyInfo.GetValue(databaseValues, null);
                            if (currentValue == null || string.IsNullOrEmpty(currentValue.ToString()))
                            {
                                currentValue = "Empty";
                            }

                            filterContext.Controller.ViewData.ModelState.AddModelError(propertyInfo.Name, "Current value: "
                                 + currentValue);
                        }
                    }

                    //TODO: hmm.... how can we only check values applicable to the model/modelstate rather than the entity we saved?
                    //The problem here is we may only have a few fields used in the viewmodel, but many in the entity
                    //so we could have a problem here with that.
                }

                filterContext.ExceptionHandled = true;

                filterContext.Result = new ViewResult() { ViewData = filterContext.Controller.ViewData };
            }
        }

        public HandleConcurrencyException()
        {
            _propertyMatchingMode = PropertyMatchingMode.UseViewModelNamesToCheckEntity;
        }

        public HandleConcurrencyException(PropertyMatchingMode propertyMatchingMode)
        {
            _propertyMatchingMode = propertyMatchingMode;
        }


        /// <summary>
        /// Searches the database loaded entity values for a field that has a [Timestamp] attribute.
        /// It then writes a string version of ther byte[] timestamp out to modelstate, assuming 
        /// we have a timestamp field on the page that caused the concurrency exception.
        /// </summary>
        /// <param name="filterContext"></param>
        /// <param name="entityFromDbProperties"></param>
        /// <param name="databaseValues"></param>
        private void UpdateTimestampField(ExceptionContext filterContext, PropertyInfo[] entityFromDbProperties, object databaseValues)
        {
            foreach (var propertyInfo in entityFromDbProperties)
            {
                var attributes = propertyInfo.GetCustomAttributesData();

                //If this is a timestamp field, we need to set the current value.
                foreach (CustomAttributeData attr in attributes)
                {
                    if (typeof(System.ComponentModel.DataAnnotations.TimestampAttribute).IsAssignableFrom(attr.Constructor.DeclaringType))
                    {
                        //This currently works only with byte[] timestamps. You can use dates as timestampts, but support is not provided here.
                        byte[] timestampValue = (byte[])propertyInfo.GetValue(databaseValues, null);
                        //we've found the timestamp. Add it to the model.
                        filterContext.Controller.ViewData.ModelState.Add(propertyInfo.Name, new ModelState());
                        filterContext.Controller.ViewData.ModelState.SetModelValue(propertyInfo.Name,
                            new ValueProviderResult(Convert.ToBase64String(timestampValue), Convert.ToBase64String(timestampValue), null));
                        break;
                    }
                }

            }
        }

    }
}


这篇关于我怎么能强制DbUpdateConcurrencyException即使我传递的FormCollection到我的帖子的行动方法,而不是一个对象的拟募集的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆