gost / sensorthings-net-sdk

.NET SDK for OGC SensorThings
MIT License
2 stars 2 forks source link

Add support for POST of any Entity #9

Closed DLade closed 3 years ago

DLade commented 4 years ago

The reason I need to write my own SensorThings Service is the lack of POST methods here.

Maybe I can combine my work with yours ...

bertt commented 4 years ago

yeah HTTP POST/PUT/DELETE is on the roadmap (https://github.com/gost/sensorthings-net-sdk/blob/master/README.md#roadmap), at the time I created this only HTTP GET was needed.

DLade commented 4 years ago

What do you thing about that:

        public async Task<Response<T>> Create<T>(T entity) where T : AbstractEntity {
            if (entity == null) {
                throw new ArgumentNullException(nameof(entity));
            }
            // workaround: creting the object the Id should be ignored at all
            entity.Id = null;

            return await Http.PostJson(GetEntityUrl(entity.GetType()), entity);
        }

        public async Task<Response<T>> Read<T>(T entity) where T : AbstractEntity {
            if (entity == null) {
                throw new ArgumentNullException(nameof(entity));
            }
            return await Get<T>(entity.GetType(), entity.Id, null);
        }

        public async Task<Response<T>> Update<T>(T entity) where T : AbstractEntity {
            if (entity == null) {
                throw new ArgumentNullException(nameof(entity));
            }
            return await Http.PatchJson(GetEntityUrl(entity.GetType()), entity);
        }

        public async Task<Response<T>> Delete<T>(T entity) where T : AbstractEntity {
            if (entity == null) {
                throw new ArgumentNullException(nameof(entity));
            }
            return await Http.DeleteJson<T>(GetEntityUrl(entity.GetType()));
        }

        private async Task<Response<T>> Get<T>(Type get, string id, OdataQuery odata) {
            var url = GetEntityUrl(get, id);
            url = odata != null ? odata.AppendOdataQueryToUrl(url) : url;

            return await Http.GetJson<T>(url);
        }

        private async Task<Response<T>> Get<T>(Type get, Type by, string id, OdataQuery odata) {
            if (by == null) {
                throw new ArgumentNullException(nameof(by));
            }
            if (string.IsNullOrEmpty(id)) {
                throw new ArgumentException("ID is required", nameof(id));
            }
            var url = $"{GetEntityUrl(by, id)}/{get.GetString(by)}";
            url = odata != null ? odata.AppendOdataQueryToUrl(url) : url;

            return await Http.GetJson<T>(url);
        }

        private string GetEntityUrl(Type type, string id = null) {
            var idString = string.IsNullOrEmpty(id) ? "" : $"({id})";

            return $"{_homedoc.GetUrlByEntityName(type.GetString())}{idString}";
        }
    }
DLade commented 4 years ago

It's rather simple using the new type-vs-enum pull request. I already implemented it but tested create(Thing) only, yet.

(I'm pretty sure you know CRUD - see https://en.wikipedia.org/wiki/Create,_read,_update_and_delete).

bertt commented 4 years ago

yes looks like a nice method to support HTTP POST/PUT/DELETE methods. Would be nice to have those methods in unit tests too. Current unit test coverage is 72.35%

DLade commented 4 years ago

Using a prerelease in my own project I found out, that the methods about can be written like that:

        // TODO - return Id of new object (note: Response has the Header 'Location' by Spec)
        public bool Create<T>(T entity)
            where T : AbstractEntity, new() {
            var response = ValidateAndGet(sensorThingsClient.Create(entity));
            return response.Success;
        }

        public T Read<T>(string id)
            where T : AbstractEntity, new() {
            var response = ValidateAndGet(sensorThingsClient.Read(new T() { Id = id }));
            return response.Result;
        }

        public bool Update<T>(T entity)
            where T : AbstractEntity, new() {
            var response = ValidateAndGet(sensorThingsClient.Update(entity));
            return response.Success;
        }

        public bool Delete<T>(string id)
            where T : AbstractEntity, new() {
            var response = ValidateAndGet(sensorThingsClient.Delete(new T() { Id = id }));
            return response.Success;
        }

note: This is the code of my own facade class. I would implement the code for "our" service as well.

DLade commented 4 years ago

Moreover there is a problem using Create: The user would need the Id of the created entity in return. The HTTP response has the Header 'Location' by Spec - so we can use that but need to parse it from string (not optimal but possible).

I'm working on this solution ...

bertt commented 4 years ago

yes the location is the address of the just created resource. So it's a string not only an id.

We also have to think about what happens when something goes wrong when creating new items (for example server returns http 500).

DLade commented 4 years ago

You're right, I did not think about the error handling yet. Currently it just breaks the runtime if an exception is thrown.

If you wanna have some fun, the code is nearly ready (I use it in my own project like this): https://github.com/gost/sensorthings-net-sdk/pull/20

Missing tests for SensorThingsEntityHandler and does not return the 'Id' with Create yet. But the still existing (29) tests and all of my own are running green.

I'm one the right way ... ;-)

DLade commented 3 years ago

It's done. You should have an Eye on the Pull-Request #20 :-)