37

私はMVC4とAngularJS(+ twitter bootstrap)のプロジェクトに参加しています。私は通常、MVCプロジェクトで「jQuery.Validate」、「DataAnnotations」、「Razor」を使用しています。次に、web.configでこれらのキーを有効にして、クライアント上のモデルのプロパティを検証します。

<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />

たとえば、モデルにこれがある場合:

[Required]
[Display(Name = "Your name")]
public string Name { get; set; }

このCshtmlで:

@Html.LabelFor(model => model.Name)
@Html.TextBoxFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)

htmlの結果は次のようになります。

<label for="Name">Your name</label>
<input data-val="true" data-val-required="The field Your name is required." id="Name" name="Name" type="text" value="" />
<span class="field-validation-valid" data-valmsg-for="Name" data-valmsg-replace="true"></span>

しかし、AngularJSを使用するときは、次のようにレンダリングしたいと思います。

<label for="Name">Your name</label>
<input type="text" ng-model="Name" id="Name" name="Name" required />
<div ng-show="form.Name.$invalid">
   <span ng-show="form.Name.$error.required">The field Your name is required</span>
</div>

これを解決するためのヘルパーまたは「データ注釈」があるかどうかはわかりません。AngularJSには次のような多くの機能があることを理解しています。

<div ng-show="form.uEmail.$dirty && form.uEmail.$invalid">Invalid:
    <span ng-show="form.uEmail.$error.required">Tell us your email.</span>
    <span ng-show="form.uEmail.$error.email">This is not a valid email.</span>
</div>

ええと、具体的には。AngularJSを使用してクライアントに表示する属性(データ注釈)を解決するには、ヘルパーまたは「データ注釈」が必要です。

それがまだ存在しない場合は、RazorForAngularJSのように実行するときが来たのかもしれません。

編集

ASP.NET MVCとAngularJSを操作する最良の方法は、おそらく手作業で行うことだと思いますfront-end(すべてのHTMLを手作業で作成します)。

4

5 に答える 5

29

ASP.Net/Angular Webサイトを作成した人として、Razorを使用してHTMLを可能な限りレンダリングすることから離れる方がはるかに良いと言えます。

私のプロジェクトでは、メインページをレンダリングするために1つのかみそりビューを設定し(Angularで記述されたシングルページアプリを使用しています)、Angularのテンプレートとして使用するストレート.htmlファイルのフォルダーがあります。

私の場合、残りはASP.Net Web API呼び出しで行われますが、JSONの結果でMVCアクションを使用することもできます。

このアーキテクチャに切り替えるとすぐに、開発の面で物事はずっとスムーズに進みました。

于 2013-02-24T01:30:51.697 に答える
9

私はかみそりから離れることについての厳しい考えに同意しますが、ページをより迅速に作成するためのいくつかのツールを作成することができます。私見では、ツールセットから削除するのではなく、必要な場所でかみそりの機能を使用することをお勧めします。

ところで、ngvalを見てください。これは、angularjsバリデーターとしてクライアント側にデータ注釈をもたらします。HTMLヘルパーとAngularモジュールがあります。プロジェクトは初期の開発段階にあることを言及しなければなりません。

于 2014-01-03T10:02:51.687 に答える
4

MVCからAngularJsへの移行をスムーズにするためのディレクティブを作成しました。マークアップは次のようになります。

<validated-input name="username" display="User Name" ng-model="model.username" required>

これは、フィールドが変更されるまで検証を遅らせるなど、Razorの規則と同じように動作します。時間が経つにつれて、マークアップを維持することは非常に直感的でシンプルであることがわかりました。

このテーマに関する私の記事

Plinkr

于 2014-09-06T00:26:49.073 に答える
2

私はあなたがやりたいことをするためにおそらく半ダースの方法があると思います。おそらく最も簡単なのは、jquery.validationマークアップを認識するAngularディレクティブを使用することです。

これがそのようなプロジェクトです:https ://github.com/mdekrey/unobtrusive-angular-validation

そしてここに別のものがあります:https ://github.com/danicomas/angular-jquery-validate

個人的には、jquery.validation.unobtrusive属性の代わりにMVC出力角度検証属性を作成するコードを記述してこの問題を解決したため、どちらも試していません。

3番目のオプションは、サーバー側の検証のみに依存することです。これは明らかに遅いですが、より複雑な検証シナリオでは、これが唯一のオプションになる場合があります。この場合、WebAPIコントローラーが通常返すModelStateDictionaryオブジェクトを解析するためにJavaScriptを記述する必要があります。それを実行し、AngularJSのネイティブ検証モデルに統合する方法についていくつかの例があります。

ModelStateDictionaryを解析するための不完全なコードを次に示します。

`` ``

angular.module('app')
    .directive('joshServerValidate', ['$http', function ($http) {
        return {
            require: 'ngModel',
            link: function (scope, ele, attrs, c) {
                console.info('wiring up ' + attrs.ngModel + ' to controller ' + c.$name);
                scope.$watch('modelState', function () {
                    if (scope.modelState == null) return;
                    var modelStateKey = attrs.joshServerValidate || attrs.ngModel;
                    modelStateKey = modelStateKey.replace(attrs.joshServerValidatePrefix, '');
                    modelStateKey = modelStateKey.replace('$index', scope.$index);
                    modelStateKey = modelStateKey.replace('model.', '');
                    console.info('validation for ' + modelStateKey);
                    if (scope.modelState[modelStateKey]) {
                        c.$setValidity('server', false);
                        c.$error.server = scope.modelState[modelStateKey];
                    } else {
                        c.$setValidity('server', true);
                    }
                });
            }
        };
    }]);

`` ``

ここで提供されている他の回答にはかなりがっかりしています。電子メールアドレスよりも少し難しいことを検証しようとしている場合、「それをしないでください」はそれほど素晴らしい提案ではありません。

于 2015-11-10T01:21:07.370 に答える
1

私はこれを少し異なる方法で解決しました。MVCアプリケーションを変更して、フィルターと、検索するビューの場所にJsonシリアライザーかみそりテンプレートを挿入するカスタムビューエンジンを介して、アプリケーション/jsonコンテンツタイプに応答するようにしました。

これは、同じコントローラー/アクションのjQuery UI、Bootstrap、およびJson応答を使用してWebサイトをスキニングできるようにするために行われました。

jsonの結果のサンプルは次のとおりです。

{
  "sid": "33b336e5-733a-435d-ad11-a79fdc1e25df",
  "form": {
    "id": 293021,
    "disableValidation": false,
    "phone": null,
    "zipCode": "60610",
    "firstName": null,
    "lastName": null,
    "address": null,
    "unit": null,
    "state": "IL",
    "email": null,
    "yearsAtAddress": null,
    "monthsAtAddress": null,
    "howHeard": null
  },
  "errors": [
    "The first name is required",
    "The last name is required",
    "Please enter a phone number",
    "Please enter an email address"
  ],
  "viewdata": {
    "cities": [
      {
        "selected": false,
        "text": "CHICAGO",
        "value": "CHICAGO"
      }
    ],
    "counties": [
      {
        "selected": false,
        "text": "COOK"
      }
    ]
  }
}

このフィルターは、リダイレクト結果をjsonオブジェクトに変換するために使用されます。このオブジェクトは、次のURLを呼び出し元のプログラムに渡します。

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);

        // if the request was application.json and the response is not json, return the current data session.
        if (filterContext.HttpContext.Request.ContentType.StartsWith("application/json") && 
            !(filterContext.Result is JsonResult || filterContext.Result is ContentResult))
        {
            if (!(filterContext.Controller is BaseController controller)) return;

            string url = filterContext.HttpContext.Request.RawUrl ?? "";
            if (filterContext.Result is RedirectResult redirectResult)
            {
                // It was a RedirectResult => we need to calculate the url
                url = UrlHelper.GenerateContentUrl(redirectResult.Url, filterContext.HttpContext);
            }
            else if (filterContext.Result is RedirectToRouteResult routeResult)
            {
                // It was a RedirectToRouteResult => we need to calculate
                // the target url
                url = UrlHelper.GenerateUrl(routeResult.RouteName, null, null, routeResult.RouteValues, RouteTable.Routes,
                    filterContext.RequestContext, false);
            }
            else
            {
                return;
            }
            var absolute = url;
            var currentUri = filterContext.HttpContext.Request.Url;
            if (url != null && currentUri != null && url.StartsWith("/"))
            {
                absolute = currentUri.Scheme + "://" + currentUri.Host + url;
            }

            var data = new {
                nextUrl =  absolute,
                uid = controller.UniqueSessionId(),
                errors = GetFlashMessage(filterContext.HttpContext.Session)
            };

            var settings = new JsonSerializerSettings
            {
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
                PreserveReferencesHandling = PreserveReferencesHandling.Objects,
                Formatting = Formatting.Indented,
                NullValueHandling = NullValueHandling.Ignore
            };
            filterContext.Result = new ContentResult
            {
                ContentType = "application/json",
                Content = JsonConvert.SerializeObject(data,settings)
            };
        }

これがViews\Json \ Serializer.cshmlで、コードベースの簡潔さとセキュリティのために使用されているステートメントは除外されています。これにより、応答を返す試みが3回行われます。1つ目は、元のView {controller} {action} .cshtmlを読み取り、htmlヘルパーを解析して、フォームとフィールドに配置することです。2番目の試行では、組み込みのブログシステム(以下のPostContent)から要素を検索し、モデルを使用するだけで失敗します。

@model dynamic
@{
    Response.ContentType = "application/json";

    Layout = "";
    var session = new Object(); // removed for security purposes

    var messages = ViewBag.Messages as List<string>() ?? new List<string>();
    var className = "";
    if (!ViewData.ModelState.IsValid)
    {
        messages.AddRange(ViewData.ModelState.Values.SelectMany(val => val.Errors).Select(error => error.ErrorMessage));
    }


    dynamic result;
    string serial;

    try
    {
        Type tModel = Model == null ? typeof(Object) : Model.GetType();
        dynamic form = new ExpandoObject();
        dynamic fields = new ExpandoObject();

        var controller = ViewContext.RouteData.Values["controller"] as string ?? "";
        var action = ViewContext.RouteData.Values["action"] as string;

        var viewPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Views", controller, action + ".cshtml");
        if (File.Exists(viewPath))
        {
            string contents = File.ReadAllText(viewPath);
            var extracted = false;
            var patterns = new[]
            {
                @"@Html\.\w+For\(\w+ => \w+\.(.*?)[,\)]",
                @"@Html\.(\w+)For\(\w+ => \w+\.([\w\.]+)[, ]*(\(SelectList\))*(ViewBag\.\w+)*[^\)]*",
                "name=\"(.*?)\""
            };

            for (var i = 0; i < 3 && !extracted; i++)
            {
                switch (i)
                {
                    case 0:
                        form = contents.ExtractFields(patterns[0], Model as object, out extracted);
                        fields = contents.ExtractElements(patterns[1], Model as object, out extracted, ViewData);
                        break;
                    case 1:
                        form = Model as mvcApp.Models.Blog == null ? null : (Model.PostContent as string).ExtractFields(patterns[2], Model as object, out extracted);
                        break;
                    default:
                        form = Model;
                        break;
                }
            }
        }
        else if (Model == null)
        {
            // nothing to do here - safeModel will serialize to an empty object
        }
        else if (Model is IEnumerable)
        {
            form = new List<object>();

            foreach (var element in ((IEnumerable) Model).AsQueryable()
                    .Cast<dynamic>())
            {
                form.Add(CustomExtensions.SafeClone(element));
            }

        } else {
            form = Activator.CreateInstance(tModel);
            CustomExtensions.CloneMatching(form, Model);
        }

        // remove any data models from the viewbag to prevent
        // recursive serialization
        foreach (var key in ViewData.Keys.ToArray())
        {
            var value = ViewData[key];
            if (value is IEnumerable)
            {
                var enumerator = (value as IEnumerable).GetEnumerator();
                value = enumerator.MoveNext() ? enumerator.Current : null;
            }
            if (value != null)
            {
                var vtype = value.GetType();
                if (vtype.Namespace != null && (vtype.Namespace == "System.Data.Entity.DynamicProxies" || vtype.Namespace.EndsWith("Models")))
                {
                    ViewData[key] = null;
                }
            }
        }

        result = new
        {
            uid = session.UniqueId,
            form,
            fields,
            errors = messages.Count == 0 ? null : messages,
            viewdata = ViewBag
        };
        var setting = new JsonSerializerSettings
        {
            PreserveReferencesHandling = PreserveReferencesHandling.None,
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            ContractResolver = new CamelCasePropertyNamesContractResolver(),
            Formatting = Formatting.Indented
        };
        if (form is IEnumerable)
        {
            setting.NullValueHandling = NullValueHandling.Ignore;
        }
        serial = JsonConvert.SerializeObject(result, setting);
    }
    catch (Exception e)
    {
        result = new {
            uid = session.UniqueId,
            error = e.Message.Split('|')
        };
        serial = JsonConvert.SerializeObject(result);
    }
    @Html.Raw(serial)
}

クローンメソッドについては、異種オブジェクトのプロパティをクローンするための最良の方法を参照してください。

    public static dynamic ExtractFields(this string html, string pattern, object model, out bool extracted)
    {
        if (html == null || model == null)
        {
            extracted = false;
            return null;
        }
        dynamic safeModel = new ExpandoObject();
        var safeDict = (IDictionary<string, Object>)safeModel;

        var matches = new Regex(pattern).Matches(html);
        extracted = matches.Count > 0;

        if ( extracted )
        {
            foreach (Match match in matches)
            {
                var name = match.Groups[1].Value;
                var value = CustomExtensions.ValueForKey(model, name);
                var segments = name.Split('.');
                var obj = safeDict;
                for (var i = 0; i < segments.Length; i++)
                {
                    name = segments[i];
                    if (i == segments.Length - 1)
                    {
                        if (obj.ContainsKey(name))
                        {
                            obj[name] = value;
                        }
                        else
                        {
                            obj.Add(name, value);
                        }
                        continue;
                    }
                    if (!obj.ContainsKey(name))
                    {
                        obj.Add(name, new ExpandoObject());
                    }
                    obj = (IDictionary<string, Object>)obj[name];
                }
            }
        }
        return safeModel;
    }

そして、プロパティチェーンの処理を少し簡単にするためのキー値コーディングの実装を次に示します。

/// <summary>
/// This borrows KeyValueCoding from Objective-C and makes working with long chains of properties more convenient. 
/// KeyValueCoding is null tolerant, and will stop if any element in the chain returns null instead of throwing a NullReferenceException. 
/// Additionally, the following Linq methods are supported: First, Last, Sum &amp; Average.
/// <br/>
/// KeyValueCoding flattens nested enumerable types, but will only aggregate the last element: "children.grandchildren.first" will return 
/// the first grandchild for each child. If you want to return a single grandchild, use "first.children.grandchildren". The same applies to
/// Sum and Average.
/// </summary>
/// <param name="source">any object</param>
/// <param name="keyPath">the path to a descendant property or method "child.grandchild.greatgrandchild".</param>
/// <param name="throwErrors">optional - defaults to supressing errors</param>
/// <returns>returns the specified descendant. If intermediate properties are IEnumerable (Lists, Arrays, Collections), the result *should be* IEnumerable</returns>
public static object ValueForKey(this object source, string keyPath, bool throwErrors = false)
{
    try
    {
        while (true)
        {
            if (source == null || keyPath == null) return null;
            if (keyPath == "") return source;

            var segments = keyPath.Split('.');
            var type = source.GetType();
            var first = segments.First();
            var property = type.GetProperty(first);
            object value = null;
            if (property == null)
            {
                var method = type.GetMethod(first);
                if (method != null)
                {
                    value = method.Invoke(source, null);
                }
            }
            else
            {
                value = property.GetValue(source, null);
            }

            if (segments.Length == 1) return value;


            var children = string.Join(".", segments.Skip(1));
            if (value is IEnumerable || "First|Last|Sum|Average".IndexOf(first, StringComparison.OrdinalIgnoreCase) > -1)
            {
                var firstChild = children.Split('.').First();
                var grandchildren = string.Join(".", children.Split('.').Skip(1));
                if (value == null) {
                    var childValue = source.ValueForKey(children);
                    value = childValue as IEnumerable<object>;
                    switch (first.Proper())
                    {
                        case "First":
                            return value == null ? childValue : ((IEnumerable<object>)value).FirstOrDefault();
                        case "Last":
                            return value == null ? childValue : ((IEnumerable<object>)value).LastOrDefault();
                        case "Count":
                            return value == null ? (childValue == null ? 0 : 1) : (int?)((IEnumerable<object>)value).Count();
                        case "Sum":
                            return value == null
                                ? Convert.ToDecimal(childValue ?? "0")
                                : ((IEnumerable<object>) value).Sum(obj => Convert.ToDecimal(obj ?? "0"));
                        case "Average":
                            return value == null
                                ? Convert.ToDecimal(childValue ?? "0")
                                : ((IEnumerable<object>) value).Average(obj => Convert.ToDecimal(obj ?? "0"));
                    }
                } else {
                    switch (firstChild.Proper())
                    {
                        case "First":
                            return ((IEnumerable<object>)value).FirstOrDefault().ValueForKey(grandchildren);
                        case "Last":
                            return ((IEnumerable<object>)value).LastOrDefault().ValueForKey(grandchildren);
                        case "Count":
                            if (!string.IsNullOrWhiteSpace(grandchildren))
                            {
                                value = value.ValueForKey(grandchildren);
                                if (value != null && ! (value is IEnumerable<object>))
                                {
                                    return 1;
                                }
                            }
                            return value == null ? 0 : ((IEnumerable<object>)value).Count();
                        case "Sum":
                            return ((IEnumerable<object>)value).Sum(obj => Convert.ToDecimal(obj.ValueForKey(grandchildren)??"0"));
                        case "Average":
                            return ((IEnumerable<object>)value).Average(obj => Convert.ToDecimal(obj.ValueForKey(grandchildren) ?? "0"));
                    }
                }
                if (value == null) return null;
                var flat = new List<object>();
                foreach (var element in (IEnumerable<object>)value)
                {
                    var child = element.ValueForKey(children);
                    if (child == null)
                    {
                        continue;
                    }
                    if (child is IEnumerable && !(child is string))
                    {
                        flat.AddRange((IEnumerable<object>) child);
                    }
                    else
                    {
                        flat.Add(child);
                    }
                }
                return flat.Count == 0? null: flat;
            }
            source = value;
            keyPath = children;
        }
    }
    catch (Exception)
    {
        if (throwErrors) throw;
    }
    return null;
}
于 2018-06-18T19:28:26.930 に答える