Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to translate all DataAnnotations without having to specify ErrorMessage #4848

Open
tbolon opened this issue Aug 22, 2017 · 30 comments
Open
Labels
affected-medium This issue impacts approximately half of our customers area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-model-binding severity-minor This label is used by an internal tool
Milestone

Comments

@tbolon
Copy link

tbolon commented Aug 22, 2017

Currently, ValidationAttributeAdapterOfTAttribute.GetErrorMessage uses IStringLocalizer only when ErrorMessage is set:

protected virtual string GetErrorMessage(ModelMetadata modelMetadata, params object[] arguments)
{
    if (modelMetadata == null)
    {
        throw new ArgumentNullException(nameof(modelMetadata));
    }

    if (_stringLocalizer != null &&
        !string.IsNullOrEmpty(Attribute.ErrorMessage) &&
        string.IsNullOrEmpty(Attribute.ErrorMessageResourceName) &&
        Attribute.ErrorMessageResourceType == null)
    {
        return _stringLocalizer[Attribute.ErrorMessage, arguments];
    }

    return Attribute.FormatErrorMessage(modelMetadata.GetDisplayName());
}

The consequence is that you have to set the ErrorMessage property each time you want to translate an error message.

Suppose you just want to translate the default RequiredAttribute ErrorMessageString, which is The {0} field is required. for all your Model classes.

  1. You must override the default DataAnnotationLocalizerProvider to return a shared resource.
  2. You add a generic entry named, for example, "DataAnnotations_Required" with the translated value format: Le champ {0} doit être renseigné.
  3. You must replace all the [Required] attributes with [Required(ErrorMessage = "DataAnnotations_Required")]

More generally, there is no way to just adapt the generic DataAnnotation validation messages to your language. (the ones in System.ComponentModel.Annotations.SR.resources) without having to replace all your data annotations.

The documentation only provide a sample where the messages are customized for each property (despite the fact that only the property name change).

There is no easy workaround either:

  • You can't inherit from RequiredAttribute to fix the ErrorMessage property, since the framework uses strict type comparison
  • Replacing the default ValidationAttributeAdapterProvider is not trivial, because you have to replace all adapters with custom ones to override the GetErrorMessage method.

Is there a better way to achieve localization for all default data annotation error messages ?
Or room for improvement in asp.net core mvc to reduce the burden of writing all this custom code ?

@tbolon tbolon changed the title Ability to localize all DataAnnotations without specifiing ErrorMessage Ability to translate all DataAnnotations without specifiing ErrorMessage Aug 22, 2017
@Eilon
Copy link
Member

Eilon commented Aug 24, 2017

@tbolon unfortunately the problem is that the built-in data annotations will return already-localized resources if the right resources are installed on the system (via NuGet, or in .NET Framework). Because of that, ASP.NET Core's localization system won't know what to translate off of because it will get "random" text depending on what's installed. That's why ASP.NET Core requires that you specify a specific error message so that it knows exactly what to key off of.

@tbolon
Copy link
Author

tbolon commented Aug 25, 2017

You are right. In my case there was no satellite resource assembly loaded, so resources were always returned in English. Despite that, perhaps the ValidationAttributeAdapterProvider could, at least, be enhanced to match sub-classes. So we could create a custom LocRequiredAttribute : RequiredAttribute class with a fixed ErrorMessage set.

One additional point to take into consideration: some validation messages are obtained through DefaultModelBindingMessageProvider, and there is an opportunity to translate them using MvcOptions and Set...Accessor() methods. eg. SetValueMustBeANumberAccessor used to display validation error for numbers.

This part is never addressed in the documentation and I discovered it only by chance.

It seems there are some inconsistencies or, at least, some rough edges regarding localization & model binding in asp.net core for now. I don't know how exactly they could be solved...

@Eilon
Copy link
Member

Eilon commented Sep 7, 2017

Crazy idea time:

If the system sees that ErrorMessage isn't set, it can try to call into the loc system with some well-known key (such as System.ComponentModel.DataAnnotations.Required.Message) to see if the loc system returns a value not equal to that key. If it returns the key, clearly it has not been localized and so it should fall back to getting the data annotation attribute's intrinsic error message. If it returns some other value, it can assume it was localized and use that as the error message.

@DamianEdwards @ryanbrandenburg @hishamco - does this sound like it might work?

@ryanbrandenburg
Copy link
Member

I can't think of any technical reason why that wouldn't work, but I'm kind of wary of adding magic keys which do specific things.

@mkArtakMSFT
Copy link
Member

We've moved this issue to the Backlog milestone. This means that it is not going to happen for the coming release. We will reassess the backlog following the current release and consider this item at that time. However, keep in mind that there are many other high priority features with which it will be competing for resources.

@effyteva
Copy link

Any updates on this one?
It's very critical for large applications...

@anna-git
Copy link

@iamdroppy
Copy link

I agree with OP, this should be enhanced.

@alexandrejobin
Copy link

In MVC5 and lower, it was really easy to make the validation work in other language. Just publish your website with the right resources files that came from Microsoft and you were good.

  • \fr\System.ComponentModel.DataAnnotations.resources.dll
  • \fr\System.Web.Mvc.resources.dll
  • \fr\EntityFramework.resources.dll

With MVC Core 2.1, if i want to localize the error messages from DataAnnotations and MVC libraries, i need to:

  • Use the ErrorMessage property on each DataAnnotations validation attribute to tell them the resource key to use. By doing this, it also require me to create an english resource file and define all validation error messages that is already present in original library (weird).
  • Change all ModelBindingMessageProvider properties to support my languages like so:
services.AddMvc(options =>
    {
        options.ModelBindingMessageProvider.SetValueIsInvalidAccessor(x => $"The value '{x}' is invalid.");
        options.ModelBindingMessageProvider.SetValueMustBeANumberAccessor(x => $"The field {x} must be a number.");
        options.ModelBindingMessageProvider.SetMissingBindRequiredValueAccessor(x => $"A value for the '{x}' property was not provided.");
        options.ModelBindingMessageProvider.SetAttemptedValueIsInvalidAccessor((x, y) => $"The value '{x}' is not valid for {y}.");
        options.ModelBindingMessageProvider.SetMissingKeyOrValueAccessor(() => "A value is required.");
        options.ModelBindingMessageProvider.SetUnknownValueIsInvalidAccessor(x => $"The supplied value is invalid for {x}.");
        options.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(x => $"The value '{x}' is invalid.");
        options.ModelBindingMessageProvider.SetMissingRequestBodyRequiredValueAccessor(() => "A non-empty request body is required.");
        options.ModelBindingMessageProvider.SetNonPropertyAttemptedValueIsInvalidAccessor(x => $"The value '{x}' is not valid.");
        options.ModelBindingMessageProvider.SetNonPropertyUnknownValueIsInvalidAccessor(() => "The supplied value is invalid.");
        options.ModelBindingMessageProvider.SetNonPropertyValueMustBeANumberAccessor(() => "The field must be a number.");
    })

The problem with that?

  • When Core 2.0 came out, 4 new validation messages were added to the framework (MissingRequestBodyRequiredValueAccessor, NonPropertyAttemptedValueIsInvalidAccessor, NonPropertyUnknownValueIsInvalidAccessor and NonPropertyValueMustBeANumberAccessor) . We have to be very careful and search for new strings to localize by our own each time we upgrade the framework.

My thought on this is if the library has messages that is intended to be used in UI like the RequiredAttribute, RangeAttribute, etc, the library should come localized by the owner (Microsoft). If i want to override the messages, i can do it with my own resource files.

@tbolon tbolon changed the title Ability to translate all DataAnnotations without specifiing ErrorMessage Ability to translate all DataAnnotations without having to specify ErrorMessage Nov 6, 2018
@aspnet-hello aspnet-hello transferred this issue from aspnet/Mvc Dec 13, 2018
@aspnet-hello aspnet-hello added this to the Backlog milestone Dec 13, 2018
@aspnet-hello aspnet-hello added 1 - Ready area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates enhancement This issue represents an ask for new feature or an enhancement to an existing one labels Dec 13, 2018
@roeldewit
Copy link

I found a GitHub project that makes it easy to just add a Resource file for the translated validation messages: https://github.com/ridercz/Altairis.ConventionalMetadataProviders

@alexsandro-xpt
Copy link

+1

@poke
Copy link
Contributor

poke commented Apr 1, 2019

Iterating on @Eilon’s idea here: To avoid hardcoding magic keys for the validations, maybe we could provide a way to essentially configure it like this:

services.AddMvc()
    .AddDataAnnotationsLocalization(options =>
    {
        options.DefaultErrorResourcePathProvider = (ValidationAttribute attribute) =>
        {
            return "Default_" + attribute.GetType().Name;
        });
    });

That way you could set up your own “magic” string for finding the message for a validation attribute. And if there is no provider specified, it would fall back to the default behavior.

The logic to attempt the localization could then look like this:

if (_stringLocalizer != null)
{
    // always allow explicit overriding
    if (!string.IsNullOrEmpty(Attribute.ErrorMessage) &&
        string.IsNullOrEmpty(Attribute.ErrorMessageResourceName) &&
        Attribute.ErrorMessageResourceType == null)
    {
        return _stringLocalizer[Attribute.ErrorMessage, arguments];
    }

    // use default error resource path provider
    if (_defaultErrorResourcePathProvider != null)
    {
        var path = _defaultErrorResourcePathProvider(attribute);
        LocalizedString message = _stringLocalizer[path, arguments];
        if (!message.ResourceNotFound)
        {
            return message.Value;
        }
    }
}

// fall back to default
return Attribute.FormatErrorMessage(modelMetadata.GetDisplayName());

While this probably won’t solve all problems, it should probably get us closer to what would be desired to make validation errors localizable.

@BlueBird67
Copy link

Still no simple solution for this huge problem?

@maftieu
Copy link

maftieu commented Jun 18, 2019

@Eilon

unfortunately the problem is that the built-in data annotations will return already-localized resources if the right resources are installed on the system (via NuGet, or in .NET Framework).

Is there any NuGet package that provides default texts translated in any other languages? I can't find any...

I have an app, hosted in Azure, that can be in FR or EN. I only receive english data annotation messages when deployed on Azure. But I would like to have a NuGet providing FR translations for "The field {0} is required" that could be deployed with my app.

@Eilon
Copy link
Member

Eilon commented Jun 18, 2019

I'm not aware of one. The Orchard CMS project has translations for many common .NET concepts (because it's built on .NET!). I did a search on their translation repo and found several matches for The field {0} is required:

https://github.com/OrchardCMS/OrchardCore.Translations/search?q=the+field+required&unscoped_q=the+field+required

However, I don't see license info on their site.

@sebastienros - what license should be on the https://github.com/OrchardCMS/OrchardCore.Translations repo?

@sebastienros
Copy link
Member

@Eilon The project you are pointing to is one that contains all the translations strings for Orchard Core, in order to be able to generate nuget packages out of it. These files are automatically pushed by Crowdin which is a collaborative website to edit translation files.

But I will look at how we handle the localization for these default strings and follow up here.

@Eilon
Copy link
Member

Eilon commented Jun 18, 2019

@sebastienros - if there's a license on the repo it would make it easier for people to grab some arbitrary translations if they wanted. Right now there's no license so it's not clear what is allowed.

@progmars
Copy link

One more sad gotcha.

I've overridden all model binding messages with translations using ModelBindingMessageProvider.SetValueIsInvalidAccessor and other ModelBindingMessageProvider values to return my custom resource strings.

And then I discovered the dreadful truth. If my API controller receives the data as JSON, then ModelBindingMessageProvider validation messages are not being used at all. Instead, Json.Net kicks in and I get something like this in response:

  "errors": {
    "countryId": [
      "Input string '111a' is not a valid number. Path 'countryId', line 3, position 23."
    ]
  },

I looked in GitHub source of Json.Net - indeed, it seems to have such exact error messages defined with line numbers etc.

So, ModelState manages to pull them in instead of using its own ModelBindingMessageProvider messages.

I tried to disable Json.Net error handling:

.AddJsonOptions(options =>
                {
                 ...
                    options.SerializerSettings.Error = delegate (object sender, Newtonsoft.Json.Serialization.ErrorEventArgs args)
                    {
                        // ignore them all
                        args.ErrorContext.Handled = true;
                    };
                })

but it made no difference.

Is there any way to catch these Json deserialization errors and redirect them to ModelBindingMessageProvider, so that my localized messages would work?


Some rant follows:
This all localization & validation business gets really messy really soon. I come from PHP Laravel framework. While it had a few localization issues for global validation texts, at least I could completely extend and override the entire process of message collection because it was all in one place. In contrast, ASP.NET Core has scattered validation messages and mechanisms throughout multiple places - ModelBindingMessageProvider, model attributes, and now also Json.Net error messages...

@vjacquet
Copy link

vjacquet commented May 7, 2020

As a workaround for the the original DataAnnotations issue, I implemented a IValidationMetadataProvider using the following code

public void CreateValidationMetadata(ValidationMetadataProviderContext context)
{
    var query = from a in context.Attributes.OfType<ValidationAttribute>()
                where string.IsNullOrEmpty(a.ErrorMessage)
                   && string.IsNullOrEmpty(a.ErrorMessageResourceName)
                select a;
    foreach (var attribute in query)
    {
       var message = attribute switch
       {
           RegularExpressionAttribute regularExpression => "The field {0} must match the regular expression '{1}'.",
           MaxLengthAttribute maxLength => "The field {0} must be a string or array type with a maximum length of '{1}'.",
           CompareAttribute compare => "'{0}' and '{1}' do not match.",
           MinLengthAttribute minLength => "The field {0} must be a string or array type with a minimum length of '{1}'.",
           RequiredAttribute required => @"The {0} field is required.",
           StringLengthAttribute stringLength when stringLength.MinimumLength == 0 => "The field {0} must be a string with a maximum length of {1}.",
           StringLengthAttribute stringLength => "The field {0} must be a string with a minimum length of {2} and a maximum length of {1}.",
           RangeAttribute range => "The field {0} must be between {1} and {2}.",
           // EmailAddressAttribute
           // PhoneAttribute
           // UrlAttribute
           // FileExtensionsAttribute
           _ => null
       };
       if (message != null)
           attribute.ErrorMessage = message;
    }
}

Validation attributes in comments already works when the ErrorMessage is empty because they set the internal DefaultErrorMessage in their constructor.

Modifying the attribute when discovering the metadata is not satisfactory but now that the ErrorMessage is always set, the stringlocalizer is always called.

But I wonder if the issue could not simply be fixed by the attribute adapters: instead of ignoring attribute with an empty ErrorMessage, couldn't the GetErrorMessage simply call the stringlocalizer with a literal default error message?

In ValidationAttributeAdapterOfTAttribute, add a protected virtual string "DefaultErrorMessage"
Then remove the line

!string.IsNullOrEmpty(Attribute.ErrorMessage) &&

And replace line

return _stringLocalizer[Attribute.ErrorMessage, arguments];

by

return _stringLocalizer[!string.IsNullOrEmpty(Attribute.ErrorMessage) ? Attribute.ErrorMessage : DefaultErrorMessage, arguments];

Then, for instance, in the RequiredAttributeAdapter, override the DefaultErrorMessage as
protected override string DefaultErrorMessage => "The {0} field is required.";

For now, this code would only work for the client validation. To make it work also when using the IObjectModelValidator, you'd have to call GetErrorMessage in the Validate method DataAnnotationsModelValidator whether Attribute.ErrorMessage is set or not, i.e. by removing the line

!string.IsNullOrEmpty(Attribute.ErrorMessage) &&

@effyteva
Copy link

Hi,
Could someone at Microsoft could try to solve this issue?
It's been here for more than 3 years, with multiple suggested solutions by the community.
Any large localized solution suffers from this issue badly, as it requires repetitive attributes for no good reason.

Thanks,
Effy

@maftieu
Copy link

maftieu commented Oct 28, 2020

Inspired by this old blog post, and similarly to what proposed vjacquet, I ended up with an IValidationMetadataProvider that uses a ressource file to get the correct translation according to the current language.

This can be combined with the model binding message provider as described at the end of this paragraph.

You just have to declare it like this in your Startup.cs

services
    .AddControllersWithViews(o =>
    {
        o.ModelBindingMessageProvider.SetAttemptedValueIsInvalidAccessor((value, fieldname) =>
            /* provide your own translation */
            string.Format("Value {0} for field {1} is incorrect", value, fieldname));
        // and do the same for all the Set*Accessor...

        o.ModelMetadataDetailsProviders.Add(new MetadataTranslationProvider(typeof(Resources.DataAnotation)));
        //                                                                          ^^ this is the resx ^^
    })

You just have to create a resx file (with designer) in which key is the attribute type name. Here its called Resources.DataAnotation.

image

// Inspired from https://blogs.msdn.microsoft.com/mvpawardprogram/2017/05/09/aspnetcore-mvc-error-message/
public class MetadataTranslationProvider : IValidationMetadataProvider
{
    private readonly ResourceManager _resourceManager;
    private readonly Type _resourceType;

    public MetadataTranslationProvider(Type type)
    {
        _resourceType = type;
        _resourceManager = new ResourceManager(type);
    }

    public void CreateValidationMetadata(ValidationMetadataProviderContext context)
    {
        foreach (var attribute in context.ValidationMetadata.ValidatorMetadata)
        {
            if (attribute is ValidationAttribute tAttr)
            {
                // search a ressource that corresponds to the attribute type name
                if (tAttr.ErrorMessage == null && tAttr.ErrorMessageResourceName == null)
                {
                    var name = tAttr.GetType().Name;
                    if (_resourceManager.GetString(name) != null)
                    {
                        tAttr.ErrorMessageResourceType = _resourceType;
                        tAttr.ErrorMessageResourceName = name;
                        tAttr.ErrorMessage = null;
                    }
                }
            }
        }
    }
}

@effyteva
Copy link

effyteva commented Nov 4, 2020

Thank you @maftieu, this solution works great!

@captainsafia captainsafia added affected-medium This issue impacts approximately half of our customers severity-minor This label is used by an internal tool labels Nov 12, 2020
@LazZiya
Copy link

LazZiya commented Dec 24, 2020

The ones who works with localization knows that it is not only DataAnnotation which needs to be translated/localizaed, additionally there is ModelBinding and IdentityDescriber errors as well, and each requires different solution.

I know that most developers prefer official solutions, but sometimes the wait is too long :) So, recently I've developed a nuget package (XLocalizer) that makes it so easy to localize all validation messages in startup or in json file.. Additionally it supports online translation and auto resource creating as well.

@nenadvicentic
Copy link

nenadvicentic commented Aug 8, 2021

Is System.ComponentModel.DataAnnotations for .NET Core available anywhere on Github? I could only find:
System.ComponentModel.DataAnnotations namespace
but this is old .NET Framework reference code.

By far the simplest solution to this issue would be to:

  1. Copy DataAnnotationsResources.resx into DataAnnotationsResources.{lang}.resx.
  2. Translate ~60 default messages.
  3. Make pull request.
  4. Satellite assembly {lang}/System.ComponentModel.DataAnnotations.resources.dll assembly with proper public key signature can be generated.
  5. Perhaps download satellite assembly separately via NuGet?

As someone already mentioned, there is no easy way around the fact that default messages are hardcoded in each attribute's constructor. At least, they are hard-coded to point to internal DataAnnotationsResources.resx file.

For example:

namespace System.ComponentModel.DataAnnotations {
    /// <summary>
    /// Validation attribute to indicate that a property field or parameter is required.
    /// </summary>
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
    [SuppressMessage("Microsoft.Performance", "CA1813:AvoidUnsealedAttributes", Justification = "We want users to be able to extend this class")]
    public class RequiredAttribute : ValidationAttribute {
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <remarks>This constructor selects a reasonable default error message for <see cref="ValidationAttribute.FormatErrorMessage"/></remarks>
        public RequiredAttribute()
            : base(() => DataAnnotationsResources.RequiredAttribute_ValidationError) {
        }

Also, there is no easy way around fallback logic in ValidationAttributeAdapter<TAttribute>:

        /// <summary>
        /// Gets the error message formatted using the <see cref="Attribute"/>.
        /// </summary>
        /// <param name="modelMetadata">The <see cref="ModelMetadata"/> associated with the model annotated with
        /// <see cref="Attribute"/>.</param>
        /// <param name="arguments">The value arguments which will be used in constructing the error message.</param>
        /// <returns>Formatted error string.</returns>
        protected virtual string GetErrorMessage(ModelMetadata modelMetadata, params object[] arguments)
        {
            if (modelMetadata == null)
            {
                throw new ArgumentNullException(nameof(modelMetadata));
            }

            if (_stringLocalizer != null &&
                !string.IsNullOrEmpty(Attribute.ErrorMessage) &&
                string.IsNullOrEmpty(Attribute.ErrorMessageResourceName) &&
                Attribute.ErrorMessageResourceType == null)
            {
                return _stringLocalizer[Attribute.ErrorMessage, arguments];
            }

            // Uses default error message from attribute's default constructor and injects property display name.
            // For `RequiredAttribute` it would be `DataAnnotationsResources.RequiredAttribute_ValidationError`
            return Attribute.FormatErrorMessage(modelMetadata.GetDisplayName()); 
        }

@mkArtakMSFT mkArtakMSFT added area-web-frameworks and removed area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates labels Oct 14, 2021
@ghost
Copy link

ghost commented Dec 28, 2021

Thanks for contacting us.

We're moving this issue to the .NET 7 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

@ghost
Copy link

ghost commented Oct 11, 2022

Thanks for contacting us.

We're moving this issue to the .NET 8 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

@captainsafia captainsafia added area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates and removed area-web-frameworks labels Jun 20, 2023
@omarceloribeiro
Copy link

This is a huge problem because it impact even some small hello world project that is not made inside the USA.
if you are in any country that don't speak english and you create a simple mvc web project, you get all the messages in english wihout any easy way to provide translations. nothing works. no nuget packages, no resx files to download, to install, no classes to implement. nothing works. and it is just the start of hello world project. How this is not solved yet?

@Erwinvandervalk
Copy link

Data Annotations can be used independently from MVC (for example if you're using console apps or minimal api's).

You want to be able to plugin a translation mechanism directly into Data Annotations. Whatever will be done for this (once it eventually get's picked up), please don't just limit it to MVC only.

@cactusoft
Copy link

Best I have found is Toolbelt.Blazor.LocalizedDataAnnotationsValidator in nuget. I have a custom resources provider set up in my db (so I can change text values through my admin section without having to restart the app). But I think it should work fine with a regular resource file too.

[Required(ErrorMessage = "core.errors.dataannotations.required")]

Problem is that not all the .net core validation seems to be exposed. I am getting "The XXX field must be a number." on InputNumber elements, but since I don't have any validation explicitly set, there doesn't seem to be any obvious way to override it.

@ips219
Copy link

ips219 commented Feb 3, 2024

For the records...

As a workaround I found this solution in https://stackoverflow.com/a/57428328/7731148

It is working correctly in my project just by adding an small static initialization and the original Strings comming from github translated...

I cannot aunderstand why DA does not allow to override default messages by configuration for five years...

@captainsafia captainsafia modified the milestones: .NET 8 Planning, Backlog Mar 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
affected-medium This issue impacts approximately half of our customers area-mvc Includes: MVC, Actions and Controllers, Localization, CORS, most templates enhancement This issue represents an ask for new feature or an enhancement to an existing one feature-model-binding severity-minor This label is used by an internal tool
Projects
No open projects
Status: No status
Development

No branches or pull requests