简体   繁体   中英

Adding validation information to REST responses

I'm trying to build a (mostly) restful service, but I'm struggling with one part of the design. We expose various resources, which on the server side look like:

public class Thing1 : Resource {
  public string ABC {get;set;}
  public string DEF {get;set;}
}

Where Resource is a base class:

public class Resource {
  public List<Link> Links {get;set;}
}

Where Link s, in turn, just bind rel s and uri s. In this way, each Resource has links to other resources, etc, and the consumer can navigate through the various resources that the service offers.

Some (but not all) resources are editable, and so the consumer will retrieve the resource, make changes to it, and then PUT those changes back to the service. Of course, at that point, the service will perform validation as required (and deal with any concurrency issues).

But, as always, it would be nice if the consuming application could perform some validation up-front before even attempting the PUT request, to cut down on unnecessary round trips (in much the same way that we might use javascript validation even though the server has to repeat it).

So, I'd like to include some validation information in our responses, so that the consuming application knows that (for instance), ABC cannot be longer than 6 characters. It should be noted that, at present, the consumer can use the same resource classes (they're in a separate assembly, together with appropriate MediaTypeFormatter classes) - adding attributes (eg System.ComponentModel.DataAnnotations.RequiredAttribute ) feels wrong because then the consuming application ends up with the validation as it was at the time they took the shared assembly, rather than as it may be now in the service.

There's also some validation that is more policy based where the actual validation properties cannot be computed until runtime.

tl;dr;

What's a good design for including "helpful" validation information in REST responses, alongside the actual resource, so that consuming applications can build good user experiences?

Maybe something like,

> GET /api/Thing/1
< 200 OK
< Content-Type:  application/vnd.acme.resource+xml

<resource>
  <ABC>blah</ABC>
  <DEF>blurg</DEF>
  <links>
    <links rel="help" href="/api/help/things"/>
    <links rel="http://acme.com/rels/validationrules" href="/api/validationrules/things"/>
  </links>
</resource>

> GET /api/validationrules/things
< 200 OK
< Content-Type: application/vnd.acme.validationrules+xml

<rules resourceType="thing">
  <property name="ABC" MaxLength="6"/>
  <property name="DEF" MaxLength="8"/>
</rules>

I do something similar in my own API. Unfortunatly there is no standard media type that I am aware of that attempts to address this particular need. I suspect trying to define this type of media type causes the "kitchen sink" effect where everyone has different requirements and they all get thrown in and the end result is something too complicated for everyone.

However, defining your own media type that addresses your particular needs may be a very manageable challenge.

The important thing about this solution, in my opinion, is that /api/validationrules/things should change very rarely and therefore can be privately cached by the client. This means that the client pays a very minimal cost for retreiving this information as a distinct resource.

If you have enough budget (time, money or both), build a metaservice for resources, so that your rest gives only data (with metadata identifiers) back and if the client desires so it can request a validation metadata for the data it just received. This way you only send what the client needs and have reasonable separation of yolks and whites.

As an implementation variant, for every request /some/res/ource you can create a companion /some/res/ource/meta that will return readonly meta about that resource. Given that path is almost the same you can have your validation defined as attributes on class members and the metaservice will simply find a class from route and build the validation info from class definition.

If I correctly understand your question, you can perform such solution:

    public class ServiceResponse
    {
        private List<Exception> exceptions = new List<Exception>();

        public List<Exception> Errors { get { return exceptions; } }

        private string password;
        public string Password
        {
            get
            {
                return password;
            }
            set
            {
                if (string.IsNullOrEmpty(value))
                {
                    exceptions.Add(new ArgumentException("Password cannot be empty!"));
                }

                if (value != null && value.Length < 10)
                {
                    exceptions.Add(new ArgumentException("Password is too short!"));
                }

                if (exceptions.Count == 0)
                {
                    password = value;
                }
                //else throw an Exception that errors were occured or do nothing
            }
        }
    }

Then you can check Errors property that it has any errors, if it does then show all errors or any you would like. In this case property 'password' will not be set until everything is correct. Hardcoded errors messages can be replaced with resource strings. As result you sent response which can be correctly handled on client's side without any javascript.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM